This notebook should be read in parallel to Section 1 of the document E2_ANDREAS MAVROCORDATOS_REPORT. Please note that the computation time in this notebook may not reflect exactly the computation time of the report due to multiple runs of the notebook (the running time trends however are more accurately reflected in the report).
# Import libraries for data manipulation
import pandas as pd
import numpy as np
from numpy import *
# Import libraries for visualization
import plotly.graph_objs as go
import plotly.figure_factory as ff
import matplotlib.pyplot as plt
# Import libraries for statistical analysis
import scipy.stats as stats
from scipy.stats import norm
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
from statsmodels.stats.diagnostic import acorr_ljungbox
import time
import warnings
warnings.filterwarnings('ignore')
""""
Below, three functions are defined to simulate the paths of stocks based on three methodologies:
- Closed form solution for GBM paths
- Euler-Maruyam for GBM paths
- Milstein Scheme for GBM paths
Each of the functions below have the following attributes:
n_sims: number of simulations
S0: initial stock price
r: constant risk-free interest rate
sigma: volatilty
T: time to expiry
t_steps: number of time steps
In addition, the E-M and Milstein Scheme have an additional parameter called the sampling_frequency.
This parameter has default value of 252 x T (i.e. continuous sample for 252 business days during T number of years)
"""
def closed_form(S0, rfr, vol, horizon, timesteps, n_sims):
"""This is a function returns the closed-form solution for GBM paths"""
np.random.seed(1) # Set random seed for reproducibility
dt = horizon / timesteps # Calculate time step
t = np.arange(dt, horizon + dt, dt) # Create an array of time points
closed_paths = [] # Initialize paths array
# Generate paths
for i in range(n_sims): # Loop over each simulation
increments = np.sqrt(dt) * np.random.randn(timesteps) # Generate random increments for Brownian motion
brownian_motion = np.cumsum(increments) # Calculate Brownian motion path
# Calculate the exact solution path (start at S0) based on formula from tutorial and lectures
path = np.concatenate(([S0], S0 * np.exp((rfr - 0.5 * vol**2) * t + vol * brownian_motion)))
closed_paths.append(path)
return closed_paths
def euler_maruyama(S0, rfr, vol, horizon, timesteps, n_sims):
"""This is a function returns the solution based on the Euler-Maruyama method for GBM paths"""
np.random.seed(1) # Set seed for reproducibility
dt = horizon / timesteps # Calculate time step
t = np.arange(dt, horizon + dt, dt) # Create an array of time points
EM_paths = []
# Generate paths
for i in range(n_sims):
increments = np.sqrt(dt) * np.random.randn(timesteps) # Generate random increments for Brownian motion
brownian_motion = np.cumsum(increments) # Calculate Brownian motion path
em_path = [S0] # Initialize the path with the initial value
current_value = S0 # Set the current value to the initial value
for j in range(len(increments)):
em_increment = rfr * current_value * dt + vol * current_value * increments[j] # Calculate the E-M increment
current_value += em_increment # Update the current value
em_path.append(current_value) # Add the current value to the path
EM_paths.append(em_path)
return EM_paths
def milstein_paths(S0, rfr, vol, horizon, timesteps, n_sims):
"""This is a function returns the solution based on the Milstein Scheme method for GBM paths"""
np.random.seed(1) # Set seed for reproducibility
dt = horizon / timesteps # Calculate time step
t = np.arange(dt, horizon + dt, dt) # Create an array of time points
MS_paths = []
# Generate paths
for i in range(n_sims): # Loop over each simulation
increments = np.sqrt(dt) * np.random.randn(timesteps) # Generate random increments for Brownian motion
brownian_motion = np.cumsum(increments) # Calculate Brownian motion path
milstein_path = [S0] # Initialize the path with the initial value
current_value = S0 # Set the current value to the initial value
for j in range(len(increments)):
# Calculate the Milstein increment
milstein_increment = rfr * current_value * dt + vol * current_value * increments[j] + 0.5 * vol**2 * current_value * (increments[j] ** 2 - dt)
current_value += milstein_increment # Update the current value
milstein_path.append(current_value) # Add the current value to the path
MS_paths.append(milstein_path)
return MS_paths
# Inital assignment of parameters based on values provided in the exam
S0 = 100 # Today's stock price
T = 1 # Time (time to expiry)
sigma = 0.2 # Volatility
r = 0.05 # Constant risk-free interest rate (= mu in case of risk neutrality)
E = 100 # Strike
# We define the number of time steps based on number of working days in a year (as per Tutorial)
t_steps = 252
# Run 1 simulation for each method
sims = 1
simulations_closed_form = closed_form(S0, r, sigma, T, t_steps, sims)
simulations_euler_maruyama = euler_maruyama(S0, r, sigma, T, t_steps, sims)
simulations_milstein = milstein_paths(S0, r, sigma, T, t_steps, sims)
# Plot the simulated stock paths from the three pre-defind functions"""
fig = go.Figure()
# Closed-Form GBM paths
for i, sim in enumerate(simulations_closed_form):
fig.add_trace(go.Scatter(x=list(range(t_steps)), y=sim, mode='lines', name=f'Closed-Form {i+1}'))
# Euler-Maruyama GBM paths
for i, sim in enumerate(simulations_euler_maruyama):
fig.add_trace(go.Scatter(x=list(range(t_steps)), y=sim, mode='lines', name=f'Euler-Maruyama {i+1}'))
# Milstein GBM paths
for i, sim in enumerate(simulations_milstein):
fig.add_trace(go.Scatter(x=list(range(t_steps)), y=sim, mode='lines', name=f'Milstein {i+1}'))
fig.update_layout(xaxis_title='Time steps', yaxis_title='Stock Price')
fig.show()
sims = 20
simulations_closed_form = closed_form(S0, r, sigma, T, t_steps, sims)
simulations_euler_maruyama = euler_maruyama(S0, r, sigma, T, t_steps, sims)
simulations_milstein = milstein_paths(S0, r, sigma, T, t_steps, sims)
#Plot the simulated stock paths from the three pre-defined functions in separate graphs
# Closed-Form GBM paths
fig1 = go.Figure()
for i, sim in enumerate(simulations_closed_form):
fig1.add_trace(go.Scatter(x=list(range(t_steps)), y=sim, mode='lines', name=f'Closed-Form {i+1}'))
fig1.update_layout(title='Closed-Form GBM Paths', xaxis_title='Time steps', yaxis_title='Stock Price')
fig1.update_layout(showlegend=False)
fig1.show()
# Euler-Maruyama GBM paths
fig2 = go.Figure()
for i, sim in enumerate(simulations_euler_maruyama):
fig2.add_trace(go.Scatter(x=list(range(t_steps)), y=sim, mode='lines', name=f'Euler-Maruyama {i+1}'))
fig2.update_layout(title='Euler-Maruyama GBM Paths', xaxis_title='Time steps', yaxis_title='Stock Price')
fig2.update_layout(showlegend=False)
fig2.show()
# Milstein GBM paths
fig3 = go.Figure()
for i, sim in enumerate(simulations_milstein):
fig3.add_trace(go.Scatter(x=list(range(t_steps)), y=sim, mode='lines', name=f'Milstein {i+1}'))
fig3.update_layout(title='Milstein GBM Paths', xaxis_title='Time steps', yaxis_title='Stock Price')
fig3.update_layout(showlegend=False)
fig3.show()
def plot_hist_qq(simulations, method_name):
"""This is a function provides a log return histogram and the Q-Q plot for the simulated paths provided in input"""
# Calculate log returns
log_returns = np.log(np.array(simulations)[:, 1:] / np.array(simulations)[:, :-1]).flatten()
# Histogram
plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
plt.hist(log_returns, bins=50, density=True, alpha=0.75)
plt.title(f'{method_name} Log Returns Histogram')
# Q-Q plot
plt.subplot(1, 2, 2)
stats.probplot(log_returns, dist="norm", plot=plt)
plt.title(f'{method_name} Log Returns Q-Q Plot')
plt.show()
# Plot histogram and Q-Q plot for each method
plot_hist_qq(simulations_closed_form, "Closed-Form")
plot_hist_qq(simulations_euler_maruyama, "Euler-Maruyama")
plot_hist_qq(simulations_milstein, "Milstein")
def Ljung_Box_autocorrelation(simulations, method_name):
"""This is a function is used to calculae the Ljung Box autocorrelation test"""
# Calculate log returns
log_returns = np.log(np.array(simulations)[:, 1:] / np.array(simulations)[:, :-1]).flatten()
# ACF and PACF plots
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))
plot_acf(log_returns, ax=ax1, title=f'{method_name} Log Returns ACF')
plot_pacf(log_returns, ax=ax2, title=f'{method_name} Log Returns PACF')
plt.show()
# Ljung-Box test
lb_test = acorr_ljungbox(log_returns, lags=[10], return_df=True)
return pd.DataFrame(data={'Method': [method_name],
'Ljung-Box Test Statistic': [lb_test['lb_stat'][10]],
'p-value': [lb_test['lb_pvalue'][10]]})
# Perform Ljung-Box test for each method and concatenate results into a single DataFrame
results_df = pd.concat([Ljung_Box_autocorrelation(simulations_closed_form, "Closed-Form"),
Ljung_Box_autocorrelation(simulations_euler_maruyama, "Euler-Maruyama"),
Ljung_Box_autocorrelation(simulations_milstein, "Milstein")], ignore_index=True)
print("Ljung-Box Test Results:")
print(results_df)
Ljung-Box Test Results:
Method Ljung-Box Test Statistic p-value
0 Closed-Form 5.078249 0.885890
1 Euler-Maruyama 5.089118 0.885146
2 Milstein 5.078317 0.885885
def continuous_arithmetic_avrg(paths_df):
"""
Calculates the continuous arithmetic average for each stock price path in the 'paths_df' input DataFrame
"""
cont_arith_df = paths_df.cumsum(axis=0) / np.arange(1, paths_df.shape[0] + 1).reshape(-1, 1)
return cont_arith_df
def continuous_geometric_avrg(paths_df):
"""
Calculates the continuous geometric average for each stock price path in the 'paths_df' input DataFrame
"""
cont_geom_df = np.exp(np.log(paths_df).cumsum(axis=0) / np.arange(1, paths_df.shape[0] + 1).reshape(-1, 1))
return cont_geom_df
def discrete_arithmetic_avrg(paths_df, periods):
"""
Calculates the discrete arithmetic average for each stock price path in the 'paths_df' input DataFrame
"""
# Get the number of simulations (columns) and time steps (rows)
n_sims = paths_df.shape[1]
t_steps = paths_df.shape[0]
# Calculate the number of periods
num_periods = t_steps // periods
if t_steps % periods != 0:
# If there is a remainder, add an additional period
num_periods += 1
# Initialize an output DataFrame with zeros and the same columns as paths_df
ari_avgs = pd.DataFrame(0, index=np.arange(1), columns=paths_df.columns)
# Loop through each stock price path (simulation)
for i in range(n_sims):
# Initialize a list to store stock prices at the beginning of each period for the current simulation
period_vals = []
# Loop through each period
for j in range(num_periods):
# Calculate the start and end indices of the current period
start_idx = j * periods
end_idx = min(start_idx + periods, t_steps)
period_length = end_idx - start_idx
# If the current period is not empty, add the stock price at the beginning of the period to the list
if period_length > 0:
period_val = paths_df.iloc[start_idx, i]
period_vals.append(period_val)
# Calculate the arithmetic average of the stock prices at the beginning of each period for the current simulation
# and store the result in the output DataFrame (ari_avgs)
ari_avgs.iloc[0, i] = np.mean(period_vals)
return ari_avgs
def discrete_geometric_avrg(paths_df, periods):
"""
Calculates the discrete geometric average for each stock price path in the 'paths_df' input DataFrame
"""
# Get the number of simulations (columns) and time steps (rows)
n_sims = paths_df.shape[1]
t_steps = paths_df.shape[0]
# Calculate the number of periods
num_periods = t_steps // periods
if t_steps % periods != 0:
# If there is a remainder, add an additional period
num_periods += 1
# Initialize an output DataFrame with zeros and the same columns as paths_df
geom_avgs = pd.DataFrame(0, index=np.arange(1), columns=paths_df.columns)
# Loop through each stock price path (simulation)
for i in range(n_sims):
# Initialize a list to store stock prices at the beginning of each period for the current simulation
period_vals = []
# Loop through each period
for j in range(num_periods):
# Calculate the start and end indices of the current period
start_idx = j * periods
end_idx = min(start_idx + periods, t_steps)
period_length = end_idx - start_idx
# If the current period is not empty, add the stock price at the beginning of the period to the list
if period_length > 0:
period_val = paths_df.iloc[start_idx, i]
period_vals.append(period_val)
# Calculate the geometric average of the stock prices at the beginning of each period for the current simulation
# Take the natural logarithm of the stock prices, then calculate the arithmetic mean of the logarithms.
# Exponentiate the result to obtain the geometric average
geom_avgs.iloc[0, i] = np.exp(np.mean(np.log(period_vals)))
return geom_avgs
# Set parameters for plots
periods = 21
n_sims = 1
# Run simulations
CF_path = closed_form(S0, r, sigma, T, t_steps, sims)
# Convert CF_path list to a DataFrame (used later in the discrete plot)
CF_path_df = pd.DataFrame(CF_path)
# Convert the lists of paths into DataFrames
CF_path_df = pd.DataFrame(CF_path).transpose()
# Calculate averages using the modified functions
CA_CF_df = continuous_arithmetic_avrg(CF_path_df)
DA_CF_df = discrete_arithmetic_avrg(CF_path_df, periods)
CG_CF_df = continuous_geometric_avrg(CF_path_df)
DG_CF_df = discrete_geometric_avrg(CF_path_df, periods)
# Calculate the continuous average value for the last timestep (252)
CA_CF_last = CA_CF_df.iloc[-1][0]
CG_CF_last = CG_CF_df.iloc[-1][0]
# Calculate the discrete average value by taking the mean of all the averages
DA_CF_mean = DA_CF_df.mean()[0]
DG_CF_mean = DG_CF_df.mean()[0]
def discrete_stock_path(stock_path_df, periods):
"""
Generates a discrete stock path based on the 'stock_path_df' input DataFrame and the given period
"""
t_steps = stock_path_df.shape[0]
num_periods = t_steps // periods
if t_steps % periods != 0:
num_periods += 1
discrete_path = []
for j in range(num_periods):
start_idx = j * periods
end_idx = min(start_idx + periods, t_steps)
period_value = stock_path_df.iloc[start_idx]
discrete_path.extend([period_value] * (end_idx - start_idx))
return discrete_path
fig1 = go.Figure() # Continuous Arithmetic Averages Plot
fig2 = go.Figure() # Discrete Arithmetic Averages Plot
#Stock price
fig1.add_trace(go.Scatter(x=list(range(t_steps)), y=CF_path[0], mode='lines', name='Exact_price'))
fig2.add_trace(go.Scatter(x=list(range(t_steps)), y=CF_path[0], mode='lines', name='Exact_price'))
# Continuous plot
fig1.add_trace(go.Scatter(x=list(range(t_steps)), y=CA_CF_df[0], mode='lines', name='CA'))
fig1.add_trace(go.Scatter(x=list(range(t_steps)), y=CG_CF_df[0], mode='lines', name='CG'))
# Discrete plot
discrete_path = discrete_stock_path(CF_path_df[0], periods)
fig2.add_trace(go.Scatter(x=list(range(t_steps)), y=discrete_path, mode='lines',
name='Discrete_path', line=dict(color='purple')))
# Add horizontal dashed lines for the continuous mean
fig1.add_shape(type='line', x0=0, x1=t_steps-1, y0=CA_CF_last, y1=CA_CF_last,
yref='y', xref='x', line=dict(color='red', dash='dash'), opacity = 0.3)
fig1.add_shape(type='line', x0=0, x1=t_steps-1, y0=CG_CF_last, y1=CG_CF_last,
yref='y', xref='x', line=dict(color='green', dash='dash'), opacity = 0.3)
# Add horizontal dashed lines for the discrete mean
fig2.add_shape(type='line', x0=0, x1=t_steps-1, y0=DA_CF_mean, y1=DA_CF_mean,
yref='y', xref='x', line=dict(color='red', dash='dash'), opacity = 0.3)
fig2.add_shape(type='line', x0=0, x1=t_steps-1, y0=DG_CF_mean, y1=DG_CF_mean,
yref='y', xref='x', line=dict(color='green', dash='dash'), opacity = 0.3)
# Add legend entries for the dashed lines
fig1.add_trace(go.Scatter(x=[None], y=[None], mode='lines', line=dict(color='red', dash='dash'),
name='Continuous Arithmetic Mean'))
fig1.add_trace(go.Scatter(x=[None], y=[None], mode='lines', line=dict(color='green', dash='dash'),
name='Continuous Geometric Mean'))
fig2.add_trace(go.Scatter(x=[None], y=[None], mode='lines', line=dict(color='red', dash='dash'),
name='Discrete Arithmetic Mean'))
fig2.add_trace(go.Scatter(x=[None], y=[None], mode='lines', line=dict(color='green', dash='dash'),
name='Discrete Geometric Mean'))
fig1.update_layout(title='Continuous Averages', xaxis_title='Time steps', yaxis_title='Stock Price')
fig2.update_layout(title='Discrete Averages', xaxis_title='Time steps', yaxis_title='Stock Price')
fig1.show()
fig2.show()
def asian_put_payoff(average_prices, strike_price):
"""Calculates the payoff of an Asian put option with a fixed strike price."""
payoffs = np.maximum(0, strike_price - average_prices)
return payoffs
def asian_call_payoff(average_prices, strike_price):
""" Calculates the payoff of an Asian call option with a fixed strike price."""
payoffs = np.maximum(0, average_prices - strike_price)
return payoffs
def asian_option_payoffs(paths_df, strike_price, chosen_frequency):
"""
Calculates the payoffs for Asian call and put options using continuous and discrete arithmetic
and geometric averages and consider computation time.
"""
periods = freq_periods[chosen_frequency]
# Calculate averages and measure time
start_time_CA = time.time()
CA_df = continuous_arithmetic_avrg(paths_df)
elapsed_time_CA = time.time() - start_time_CA
start_time_CG = time.time()
CG_df = continuous_geometric_avrg(paths_df)
elapsed_time_CG = time.time() - start_time_CG
start_time_DA = time.time()
DA_df = discrete_arithmetic_avrg(paths_df, periods)
elapsed_time_DA = time.time() - start_time_DA
start_time_DG = time.time()
DG_df = discrete_geometric_avrg(paths_df, periods)
elapsed_time_DG = time.time() - start_time_DG
# Calculate payoffs for call and put options
call_payoffs_CA = asian_call_payoff(CA_df.iloc[-1], strike_price)
call_payoffs_CG = asian_call_payoff(CG_df.iloc[-1], strike_price)
call_payoffs_DA = asian_call_payoff(DA_df.mean(), strike_price)
call_payoffs_DG = asian_call_payoff(DG_df.mean(), strike_price)
put_payoffs_CA = asian_put_payoff(CA_df.iloc[-1], strike_price)
put_payoffs_CG = asian_put_payoff(CG_df.iloc[-1], strike_price)
put_payoffs_DA = asian_put_payoff(DA_df.mean(), strike_price)
put_payoffs_DG = asian_put_payoff(DG_df.mean(), strike_price)
# Calculate mean payoffs (based on discounted detailed in the exam)
discount_factor = np.exp(-r * T)
payoffs = {
"Call_CA": discount_factor * call_payoffs_CA.mean(),
"Call_CG": discount_factor * call_payoffs_CG.mean(),
"Call_DA": discount_factor * call_payoffs_DA.mean(),
"Call_DG": discount_factor * call_payoffs_DG.mean(),
"Put_CA": discount_factor * put_payoffs_CA.mean(),
"Put_CG": discount_factor * put_payoffs_CG.mean(),
"Put_DA": discount_factor * put_payoffs_DA.mean(),
"Put_DG": discount_factor * put_payoffs_DG.mean(),
}
# To calculate the computation time
avg_times = {
"CA_time": elapsed_time_CA,
"CG_time": elapsed_time_CG,
"DA_time": elapsed_time_DA,
"DG_time": elapsed_time_DG
}
return payoffs, avg_times
# Define the frequency of periods
freq_periods = {
'daily': 1,
'weekly': 5,
'biweekly': 10,
'monthly': 21,
'quarterly': 63,
'semester': 126
}
# Set parameters
S0 = 100
r = 0.05
sigma = 0.2
T = 1
t_steps = 252
sims = 3000
E = 100
# Generate paths and measure time
start_time_CF_paths = time.time()
CF_paths = closed_form(S0, r, sigma, T, t_steps, sims)
time_CF_paths = time.time() - start_time_CF_paths
start_time_EM_paths = time.time()
EM_paths = euler_maruyama(S0, r, sigma, T, t_steps, sims)
time_EM_paths = time.time() - start_time_EM_paths
start_time_MS_paths = time.time()
MS_paths = milstein_paths(S0, r, sigma, T, t_steps, sims)
time_MS_paths = time.time() - start_time_MS_paths
def measure_time(method, *args):
start_time = time.time()
result, avg_times = method(*args)
end_time = time.time()
elapsed_time = end_time - start_time
return result, avg_times, elapsed_time
# Convert the lists of paths into DataFrames
CF_paths_df = pd.DataFrame(CF_paths).transpose()
EM_paths_df = pd.DataFrame(EM_paths).transpose()
MS_paths_df = pd.DataFrame(MS_paths).transpose()
# Calculate payoffs and measure time
CF_payoffs, CF_avg_times, CF_payoffs_time = measure_time(asian_option_payoffs,
CF_paths_df, E, 'monthly')
EM_payoffs, EM_avg_times, EM_payoffs_time = measure_time(asian_option_payoffs,
EM_paths_df, E, 'monthly')
MS_payoffs, MS_avg_times, MS_payoffs_time = measure_time(asian_option_payoffs,
MS_paths_df, E, 'monthly')
# Combine the payoffs into a DataFrame
payoffs_df = pd.DataFrame({'Closed Form': CF_payoffs,
'Euler-Maruyama': EM_payoffs,
'Milstein': MS_payoffs}).transpose()
# Display the table
payoffs_df
| Call_CA | Call_CG | Call_DA | Call_DG | Put_CA | Put_CG | Put_DA | Put_DG | |
|---|---|---|---|---|---|---|---|---|
| Closed Form | 5.972005 | 5.742982 | 5.894104 | 5.646514 | 3.291008 | 3.409279 | 3.208988 | 3.333806 |
| Euler-Maruyama | 5.971964 | 5.743228 | 5.894126 | 5.646688 | 3.287663 | 3.406148 | 3.205987 | 3.330861 |
| Milstein | 5.970964 | 5.742027 | 5.893078 | 5.645582 | 3.290267 | 3.408488 | 3.208263 | 3.333029 |
# Create dictionary for each scheme
path_times = {"Closed Form": time_CF_paths,"Euler-Maruyama": time_EM_paths,"Milstein Scheme": time_MS_paths}
CA_times = {"Closed Form": CF_avg_times["CA_time"],"Euler-Maruyama": EM_avg_times["CA_time"],
"Milstein Scheme": MS_avg_times["CA_time"]}
CG_times = {"Closed Form": CF_avg_times["CG_time"],"Euler-Maruyama": EM_avg_times["CG_time"],
"Milstein Scheme": MS_avg_times["CG_time"]}
DA_times = {"Closed Form": CF_avg_times["DA_time"],"Euler-Maruyama": EM_avg_times["DA_time"],
"Milstein Scheme": MS_avg_times["DA_time"]}
DG_times = {"Closed Form": CF_avg_times["DG_time"],"Euler-Maruyama": EM_avg_times["DG_time"],
"Milstein Scheme": MS_avg_times["DG_time"]}
# Create DataFrame from dictionaries
running_times_df = pd.DataFrame({
"Path Generation": path_times,
"Payoff CA": CA_times,
"Payoff CG": CG_times,
"Payoff DA": DA_times,
"Payoff DG": DG_times
})
# Display computation time
running_times_df
| Path Generation | Payoff CA | Payoff CG | Payoff DA | Payoff DG | |
|---|---|---|---|---|---|
| Closed Form | 0.059918 | 0.015118 | 0.022587 | 1.100632 | 1.139448 |
| Euler-Maruyama | 0.329699 | 0.017061 | 0.018135 | 1.186121 | 1.292552 |
| Milstein Scheme | 0.795896 | 0.013098 | 0.019768 | 0.938483 | 0.899511 |
def floating_option_payoffs(paths_df, chosen_frequency):
"""
Calculates the payoffs for Asian call and put options with a floating strike price,
using continuous and discrete arithmetic and geometric averages, and consider computation time.
"""
periods = freq_periods[chosen_frequency]
# Calculate averages and measure time
start_time_CA = time.time()
CA_df = continuous_arithmetic_avrg(paths_df)
elapsed_time_CA = time.time() - start_time_CA
start_time_CG = time.time()
CG_df = continuous_geometric_avrg(paths_df)
elapsed_time_CG = time.time() - start_time_CG
start_time_DA = time.time()
DA_df = discrete_arithmetic_avrg(paths_df, periods)
elapsed_time_DA = time.time() - start_time_DA
start_time_DG = time.time()
DG_df = discrete_geometric_avrg(paths_df, periods)
elapsed_time_DG = time.time() - start_time_DG
# Calculate payoffs for call and put options
final_asset_price = paths_df.iloc[-1]
call_payoffs_CA = asian_call_payoff(final_asset_price, CA_df.iloc[-1])
call_payoffs_CG = asian_call_payoff(final_asset_price, CG_df.iloc[-1])
call_payoffs_DA = asian_call_payoff(final_asset_price, DA_df.mean())
call_payoffs_DG = asian_call_payoff(final_asset_price, DG_df.mean())
put_payoffs_CA = asian_put_payoff(final_asset_price, CA_df.iloc[-1])
put_payoffs_CG = asian_put_payoff(final_asset_price, CG_df.iloc[-1])
put_payoffs_DA = asian_put_payoff(final_asset_price, DA_df.mean())
put_payoffs_DG = asian_put_payoff(final_asset_price, DG_df.mean())
# Calculate mean payoffs (based on discounted detailed in the exam)
discount_factor = np.exp(-r * T)
payoffs = {
"Call_CA": discount_factor * call_payoffs_CA.mean(),
"Call_CG": discount_factor * call_payoffs_CG.mean(),
"Call_DA": discount_factor * call_payoffs_DA.mean(),
"Call_DG": discount_factor * call_payoffs_DG.mean(),
"Put_CA": discount_factor * put_payoffs_CA.mean(),
"Put_CG": discount_factor * put_payoffs_CG.mean(),
"Put_DA": discount_factor * put_payoffs_DA.mean(),
"Put_DG": discount_factor * put_payoffs_DG.mean(),
}
# To calculate the computation time
avg_times = {
"CA_time": elapsed_time_CA,
"CG_time": elapsed_time_CG,
"DA_time": elapsed_time_DA,
"DG_time": elapsed_time_DG
}
return payoffs, avg_times
# Calculate payoffs and measure time
CF_payoffs, CF_avg_times, CF_payoffs_time = measure_time(floating_option_payoffs,
CF_paths_df, 'monthly')
EM_payoffs, EM_avg_times, EM_payoffs_time = measure_time(floating_option_payoffs,
EM_paths_df, 'monthly')
MS_payoffs, MS_avg_times, MS_payoffs_time = measure_time(floating_option_payoffs,
MS_paths_df, 'monthly')
# Combine the payoffs into a DataFrame
payoffs_df = pd.DataFrame({'Closed Form': CF_payoffs,
'Euler-Maruyama': EM_payoffs,
'Milstein': MS_payoffs}).transpose()
# Display the table
payoffs_df
| Call_CA | Call_CG | Call_DA | Call_DG | Put_CA | Put_CG | Put_DA | Put_DG | |
|---|---|---|---|---|---|---|---|---|
| Closed Form | 5.986836 | 6.206926 | 5.904629 | 6.142226 | 3.254626 | 3.127422 | 3.176537 | 3.041727 |
| Euler-Maruyama | 5.985910 | 6.205819 | 5.903487 | 6.140985 | 3.255591 | 3.128279 | 3.177007 | 3.042192 |
| Milstein | 5.985773 | 6.205782 | 5.903583 | 6.141095 | 3.253873 | 3.126724 | 3.175801 | 3.041051 |
# Create dictionary for each scheme
path_times = {"Closed Form": time_CF_paths,"Euler-Maruyama": time_EM_paths,"Milstein Scheme": time_MS_paths}
CA_times = {"Closed Form": CF_avg_times["CA_time"],"Euler-Maruyama": EM_avg_times["CA_time"],
"Milstein Scheme": MS_avg_times["CA_time"]}
CG_times = {"Closed Form": CF_avg_times["CG_time"],"Euler-Maruyama": EM_avg_times["CG_time"],
"Milstein Scheme": MS_avg_times["CG_time"]}
DA_times = {"Closed Form": CF_avg_times["DA_time"],"Euler-Maruyama": EM_avg_times["DA_time"],
"Milstein Scheme": MS_avg_times["DA_time"]}
DG_times = {"Closed Form": CF_avg_times["DG_time"],"Euler-Maruyama": EM_avg_times["DG_time"],
"Milstein Scheme": MS_avg_times["DG_time"]}
# Create DataFrame from dictionaries
running_times_df = pd.DataFrame({
"Path Generation": path_times,
"Payoff CA": CA_times,
"Payoff CG": CG_times,
"Payoff DA": DA_times,
"Payoff DG": DG_times
})
# Display computation time
running_times_df
| Path Generation | Payoff CA | Payoff CG | Payoff DA | Payoff DG | |
|---|---|---|---|---|---|
| Closed Form | 0.059918 | 0.018096 | 0.020042 | 0.900321 | 0.928178 |
| Euler-Maruyama | 0.329699 | 0.010199 | 0.015438 | 0.963956 | 1.008374 |
| Milstein Scheme | 0.795896 | 0.016299 | 0.016369 | 0.963018 | 0.947251 |
#Firstly, we update the function to simplify the methodology here. We remove the use of computation time,
# but keep the remaining of the payoff function as is.
def asian_option_payoffs_param(paths_df, strike_price, chosen_frequency):
"""
Calculates the payoffs for Asian call and put options using continuous and discrete arithmetic
and geometric averages, but do not consider computation time.
"""
periods = freq_periods[chosen_frequency]
# Calculate averages
CA_df = continuous_arithmetic_avrg(paths_df)
CG_df = continuous_geometric_avrg(paths_df)
DA_df = discrete_arithmetic_avrg(paths_df, periods)
DG_df = discrete_geometric_avrg(paths_df, periods)
# Calculate payoffs for call and put options
call_payoffs_CA = asian_call_payoff(CA_df.iloc[-1], strike_price)
call_payoffs_CG = asian_call_payoff(CG_df.iloc[-1], strike_price)
call_payoffs_DA = asian_call_payoff(DA_df.mean(), strike_price)
call_payoffs_DG = asian_call_payoff(DG_df.mean(), strike_price)
put_payoffs_CA = asian_put_payoff(CA_df.iloc[-1], strike_price)
put_payoffs_CG = asian_put_payoff(CG_df.iloc[-1], strike_price)
put_payoffs_DA = asian_put_payoff(DA_df.mean(), strike_price)
put_payoffs_DG = asian_put_payoff(DG_df.mean(), strike_price)
# Calculate mean payoffs (based on discounted detailed in the exam)
discount_factor = np.exp(-r * T)
payoffs = {
"Call_CA": discount_factor * call_payoffs_CA.mean(),
"Call_CG": discount_factor * call_payoffs_CG.mean(),
"Call_DA": discount_factor * call_payoffs_DA.mean(),
"Call_DG": discount_factor * call_payoffs_DG.mean(),
"Put_CA": discount_factor * put_payoffs_CA.mean(),
"Put_CG": discount_factor * put_payoffs_CG.mean(),
"Put_DA": discount_factor * put_payoffs_DA.mean(),
"Put_DG": discount_factor * put_payoffs_DG.mean(),
}
return payoffs
# Inital assignment of parameters based on values provided in the exam. We will be testing the sigma parameter
S0 = 100 # Today's stock price
T = 1 # Time (time to expiry)
#sigma = 0.2 # Volatility
r = 0.05 # Constant risk-free interest rate (= mu in case of risk neutrality)
E = 100 # Strike
sims = 3000
volatilities = np.arange(0.10, 0.80, 0.10)
results = []
for vol in volatilities:
# Generate paths with the current volatility
CF_paths = closed_form(S0, r, vol, T, t_steps, sims)
CF_paths_df = pd.DataFrame(CF_paths).transpose()
EM_paths = euler_maruyama(S0, r, vol, T, t_steps, sims)
EM_paths_df = pd.DataFrame(EM_paths).transpose()
MS_paths = milstein_paths(S0, r, vol, T, t_steps, sims)
MS_paths_df = pd.DataFrame(MS_paths).transpose()
# Calculate payoffs for the current volatility
CF_payoffs = asian_option_payoffs_param(CF_paths_df, E, 'monthly')
EM_payoffs = asian_option_payoffs_param(EM_paths_df, E, 'monthly')
MS_payoffs = asian_option_payoffs_param(MS_paths_df, E, 'monthly')
# Append the results to the list
results.append({"Volatility": vol, "Scheme": "Closed Form", **CF_payoffs})
results.append({"Volatility": vol, "Scheme": "Euler-Maruyama", **EM_payoffs})
results.append({"Volatility": vol, "Scheme": "Milstein", **MS_payoffs})
# Create the results DataFrame
volatility_results_df = pd.DataFrame(results)
# Display the DataFrame
print("Impact of changing volatility:")
display(volatility_results_df)
Impact of changing volatility:
| Volatility | Scheme | Call_CA | Call_CG | Call_DA | Call_DG | Put_CA | Put_CG | Put_DA | Put_DG | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0.1 | Closed Form | 3.743725 | 3.670867 | 3.710083 | 3.630641 | 1.201805 | 1.224048 | 1.164966 | 1.188215 |
| 1 | 0.1 | Euler-Maruyama | 3.743685 | 3.670882 | 3.709973 | 3.630545 | 1.201107 | 1.223374 | 1.164267 | 1.187497 |
| 2 | 0.1 | Milstein | 3.743138 | 3.670304 | 3.709503 | 3.630089 | 1.201488 | 1.223719 | 1.164656 | 1.187896 |
| 3 | 0.2 | Closed Form | 5.972005 | 5.742982 | 5.894104 | 5.646514 | 3.291008 | 3.409279 | 3.208988 | 3.333806 |
| 4 | 0.2 | Euler-Maruyama | 5.971964 | 5.743228 | 5.894126 | 5.646688 | 3.287663 | 3.406148 | 3.205987 | 3.330861 |
| 5 | 0.2 | Milstein | 5.970964 | 5.742027 | 5.893078 | 5.645582 | 3.290267 | 3.408488 | 3.208263 | 3.333029 |
| 6 | 0.3 | Closed Form | 8.269677 | 7.789369 | 8.150250 | 7.636584 | 5.434399 | 5.720766 | 5.310407 | 5.617529 |
| 7 | 0.3 | Euler-Maruyama | 8.271807 | 7.791813 | 8.151936 | 7.638587 | 5.429016 | 5.715694 | 5.305216 | 5.612588 |
| 8 | 0.3 | Milstein | 8.268207 | 7.788084 | 8.148811 | 7.635337 | 5.433255 | 5.719508 | 5.309294 | 5.616289 |
| 9 | 0.4 | Closed Form | 10.584858 | 9.759435 | 10.431338 | 9.545610 | 7.579880 | 8.107131 | 7.421597 | 7.982982 |
| 10 | 0.4 | Euler-Maruyama | 10.589223 | 9.764751 | 10.435362 | 9.551374 | 7.571156 | 8.099747 | 7.413685 | 7.977058 |
| 11 | 0.4 | Milstein | 10.583005 | 9.757905 | 10.429525 | 9.544119 | 7.578364 | 8.105416 | 7.420122 | 7.981272 |
| 12 | 0.5 | Closed Form | 12.909127 | 11.637518 | 12.722348 | 11.358370 | 9.718288 | 10.551037 | 9.526534 | 10.412777 |
| 13 | 0.5 | Euler-Maruyama | 12.918743 | 11.650529 | 12.730578 | 11.368900 | 9.708104 | 10.545624 | 9.516812 | 10.406463 |
| 14 | 0.5 | Milstein | 12.906946 | 11.635813 | 12.720186 | 11.356744 | 9.716430 | 10.548853 | 9.524694 | 10.410620 |
| 15 | 0.6 | Closed Form | 15.238528 | 13.422634 | 15.016804 | 13.069634 | 11.844343 | 13.048933 | 11.617089 | 12.898512 |
| 16 | 0.6 | Euler-Maruyama | 15.257158 | 13.441674 | 15.032521 | 13.084861 | 11.835592 | 13.043875 | 11.608177 | 12.891925 |
| 17 | 0.6 | Milstein | 15.236125 | 13.420812 | 15.014401 | 13.067997 | 11.842208 | 13.046247 | 11.614950 | 12.895941 |
| 18 | 0.7 | Closed Form | 17.577872 | 15.110497 | 17.319527 | 14.674069 | 13.960886 | 15.593086 | 13.695711 | 15.430695 |
| 19 | 0.7 | Euler-Maruyama | 17.604554 | 15.135966 | 17.340344 | 14.696822 | 13.952032 | 15.589495 | 13.684911 | 15.427528 |
| 20 | 0.7 | Milstein | 17.575221 | 15.108758 | 17.317021 | 14.672498 | 13.958394 | 15.590000 | 13.693352 | 15.427686 |
def asian_option_param_float(paths_df, chosen_frequency):
"""
Calculates the payoffs for Asian call and put options with a floating strike price,
using continuous and discrete arithmetic and geometric averages.
"""
periods = freq_periods[chosen_frequency]
# Calculate averages
CA_df = continuous_arithmetic_avrg(paths_df)
CG_df = continuous_geometric_avrg(paths_df)
DA_df = discrete_arithmetic_avrg(paths_df, periods)
DG_df = discrete_geometric_avrg(paths_df, periods)
# Calculate payoffs for call and put options
final_asset_price = paths_df.iloc[-1]
call_payoffs_CA = asian_call_payoff(final_asset_price, CA_df.iloc[-1])
call_payoffs_CG = asian_call_payoff(final_asset_price, CG_df.iloc[-1])
call_payoffs_DA = asian_call_payoff(final_asset_price, DA_df.mean())
call_payoffs_DG = asian_call_payoff(final_asset_price, DG_df.mean())
put_payoffs_CA = asian_put_payoff(final_asset_price, CA_df.iloc[-1])
put_payoffs_CG = asian_put_payoff(final_asset_price, CG_df.iloc[-1])
put_payoffs_DA = asian_put_payoff(final_asset_price, DA_df.mean())
put_payoffs_DG = asian_put_payoff(final_asset_price, DG_df.mean())
# Calculate mean payoffs (based on discounted detailed in the exam)
discount_factor = np.exp(-r * T)
payoffs = {
"Call_CA": discount_factor * call_payoffs_CA.mean(),
"Call_CG": discount_factor * call_payoffs_CG.mean(),
"Call_DA": discount_factor * call_payoffs_DA.mean(),
"Call_DG": discount_factor * call_payoffs_DG.mean(),
"Put_CA": discount_factor * put_payoffs_CA.mean(),
"Put_CG": discount_factor * put_payoffs_CG.mean(),
"Put_DA": discount_factor * put_payoffs_DA.mean(),
"Put_DG": discount_factor * put_payoffs_DG.mean(),
}
return payoffs
# Initial assignment of parameters based on values provided in the exam. We will be testing the sigma parameter
S0 = 100 # Today's stock price
T = 1 # Time (time to expiry)
# sigma = 0.2 # Volatility
r = 0.05 # Constant risk-free interest rate (= mu in case of risk neutrality)
E = 100 # Strike
sims = 3000
volatilities = np.arange(0.10, 0.80, 0.10)
results = []
for vol in volatilities:
# Generate paths with the current volatility
CF_paths = closed_form(S0, r, vol, T, t_steps, sims)
CF_paths_df = pd.DataFrame(CF_paths).transpose()
EM_paths = euler_maruyama(S0, r, vol, T, t_steps, sims)
EM_paths_df = pd.DataFrame(EM_paths).transpose()
MS_paths = milstein_paths(S0, r, vol, T, t_steps, sims)
MS_paths_df = pd.DataFrame(MS_paths).transpose()
# Calculate payoffs for the current volatility
CF_payoffs = asian_option_param_float(CF_paths_df, 'monthly')
EM_payoffs = asian_option_param_float(EM_paths_df, 'monthly')
MS_payoffs = asian_option_param_float(MS_paths_df, 'monthly')
# Append the results to the list
results.append({"Volatility": vol, "Scheme": "Closed Form", **CF_payoffs})
results.append({"Volatility": vol, "Scheme": "Euler-Maruyama", **EM_payoffs})
results.append({"Volatility": vol, "Scheme": "Milstein", **MS_payoffs})
# Create the results DataFrame
volatility_results_df = pd.DataFrame(results)
# Display the DataFrame
print("Impact of changing volatility:")
display(volatility_results_df)
Impact of changing volatility:
| Volatility | Scheme | Call_CA | Call_CG | Call_DA | Call_DG | Put_CA | Put_CG | Put_DA | Put_DG | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0.1 | Closed Form | 3.761406 | 3.832980 | 3.723484 | 3.801625 | 1.167881 | 1.144355 | 1.133157 | 1.108607 |
| 1 | 0.1 | Euler-Maruyama | 3.760804 | 3.832342 | 3.722992 | 3.801072 | 1.167933 | 1.144400 | 1.133248 | 1.108671 |
| 2 | 0.1 | Milstein | 3.760810 | 3.832360 | 3.722897 | 3.801012 | 1.167566 | 1.144052 | 1.132850 | 1.108311 |
| 3 | 0.2 | Closed Form | 5.986836 | 6.206926 | 5.904629 | 6.142226 | 3.254626 | 3.127422 | 3.176537 | 3.041727 |
| 4 | 0.2 | Euler-Maruyama | 5.985910 | 6.205819 | 5.903487 | 6.140985 | 3.255591 | 3.128279 | 3.177007 | 3.042192 |
| 5 | 0.2 | Milstein | 5.985773 | 6.205782 | 5.903583 | 6.141095 | 3.253873 | 3.126724 | 3.175801 | 3.041051 |
| 6 | 0.3 | Closed Form | 8.305807 | 8.754126 | 8.180423 | 8.663608 | 5.428141 | 5.109785 | 5.307323 | 4.969720 |
| 7 | 0.3 | Euler-Maruyama | 8.304330 | 8.752106 | 8.179810 | 8.662626 | 5.430747 | 5.111851 | 5.310156 | 4.972250 |
| 8 | 0.3 | Milstein | 8.304297 | 8.752447 | 8.178932 | 8.661938 | 5.426964 | 5.108739 | 5.306165 | 4.968702 |
| 9 | 0.4 | Closed Form | 10.661334 | 11.416945 | 10.495073 | 11.308435 | 7.627208 | 7.030145 | 7.465709 | 6.831958 |
| 10 | 0.4 | Euler-Maruyama | 10.661667 | 11.416182 | 10.495472 | 11.307382 | 7.634897 | 7.036349 | 7.472311 | 6.836860 |
| 11 | 0.4 | Milstein | 10.659414 | 11.414776 | 10.493193 | 11.306249 | 7.625618 | 7.028827 | 7.464158 | 6.830659 |
| 12 | 0.5 | Closed Form | 13.062710 | 14.192757 | 12.847939 | 14.069032 | 9.855196 | 8.880884 | 9.645400 | 8.616272 |
| 13 | 0.5 | Euler-Maruyama | 13.064943 | 14.192219 | 12.851228 | 14.070378 | 9.869353 | 8.890896 | 9.658767 | 8.626587 |
| 14 | 0.5 | Milstein | 13.060450 | 14.190131 | 12.845780 | 14.066413 | 9.853204 | 8.879328 | 9.643510 | 8.614774 |
| 15 | 0.6 | Closed Form | 15.505706 | 17.084350 | 15.238282 | 16.949522 | 12.099914 | 10.658074 | 11.838020 | 10.320667 |
| 16 | 0.6 | Euler-Maruyama | 15.504924 | 17.085536 | 15.241211 | 16.952354 | 12.117312 | 10.674158 | 11.856378 | 10.336112 |
| 17 | 0.6 | Milstein | 15.503165 | 17.081316 | 15.235861 | 16.946597 | 12.097479 | 10.656277 | 11.835709 | 10.319051 |
| 18 | 0.7 | Closed Form | 17.987082 | 20.091709 | 17.662007 | 19.947807 | 14.347684 | 12.352735 | 14.029439 | 11.934796 |
| 19 | 0.7 | Euler-Maruyama | 17.989984 | 20.093014 | 17.664215 | 19.949727 | 14.377408 | 12.374387 | 14.054550 | 11.953923 |
| 20 | 0.7 | Milstein | 17.984517 | 20.088370 | 17.659629 | 19.944639 | 14.344901 | 12.350686 | 14.026855 | 11.933009 |
# Inital assignment of parameters based on values provided in the exam. We will be testing the sigma parameter
S0 = 100 # Today's stock price
T = 1 # Time (time to expiry)
sigma = 0.2 # Volatility
#r = 0.05 # Constant risk-free interest rate (= mu in case of risk neutrality)
E = 100 # Strike
sims = 3000
returns = np.arange(-0.1, 0.11, 0.05)
results = []
for r in returns:
# Generate paths with the current volatility
CF_paths = closed_form(S0, r, sigma, T, t_steps, sims)
CF_paths_df = pd.DataFrame(CF_paths).transpose()
EM_paths = euler_maruyama(S0, r, sigma, T, t_steps, sims)
EM_paths_df = pd.DataFrame(EM_paths).transpose()
MS_paths = milstein_paths(S0, r, sigma, T, t_steps, sims)
MS_paths_df = pd.DataFrame(MS_paths).transpose()
# Calculate payoffs for the current volatility
CF_payoffs = asian_option_payoffs_param(CF_paths_df, E, 'monthly')
EM_payoffs = asian_option_payoffs_param(EM_paths_df, E, 'monthly')
MS_payoffs = asian_option_payoffs_param(MS_paths_df, E, 'monthly')
# Append the results to the list
results.append({"Return": r, "Scheme": "Closed Form", **CF_payoffs})
results.append({"Return": r, "Scheme": "Euler-Maruyama", **EM_payoffs})
results.append({"Return": r, "Scheme": "Milstein", **MS_payoffs})
# Create the results DataFrame
return_results_df = pd.DataFrame(results)
# Format the "Return" column
return_results_df["Return"] = return_results_df["Return"].apply(lambda x: round(x, 2))
# Display the DataFrame
print("Impact of changing returns:")
display(return_results_df)
Impact of changing returns:
| Return | Scheme | Call_CA | Call_CG | Call_DA | Call_DG | Put_CA | Put_CG | Put_DA | Put_DG | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | -0.10 | Closed Form | 2.864473 | 2.754214 | 2.775438 | 2.660648 | 7.935884 | 8.224332 | 7.838564 | 8.153396 |
| 1 | -0.10 | Euler-Maruyama | 2.866918 | 2.756720 | 2.777419 | 2.662674 | 7.935385 | 8.224219 | 7.837909 | 8.153111 |
| 2 | -0.10 | Milstein | 2.865978 | 2.755616 | 2.776901 | 2.662005 | 7.938291 | 8.226933 | 7.840927 | 8.155973 |
| 3 | -0.05 | Closed Form | 3.754577 | 3.612983 | 3.665048 | 3.514788 | 6.068977 | 6.286738 | 5.976019 | 6.210615 |
| 4 | -0.05 | Euler-Maruyama | 3.755881 | 3.613984 | 3.666710 | 3.516234 | 6.066707 | 6.284345 | 5.974406 | 6.208956 |
| 5 | -0.05 | Milstein | 3.755442 | 3.613785 | 3.665895 | 3.515571 | 6.070031 | 6.287869 | 5.977055 | 6.211736 |
| 6 | 0.00 | Closed Form | 4.794004 | 4.613055 | 4.710666 | 4.516877 | 4.527474 | 4.689282 | 4.442010 | 4.614632 |
| 7 | 0.00 | Euler-Maruyama | 4.795076 | 4.614321 | 4.711844 | 4.518238 | 4.524860 | 4.686914 | 4.439791 | 4.612632 |
| 8 | 0.00 | Milstein | 4.794022 | 4.613072 | 4.710681 | 4.516892 | 4.527487 | 4.689296 | 4.442021 | 4.614643 |
| 9 | 0.05 | Closed Form | 5.972005 | 5.742982 | 5.894104 | 5.646514 | 3.291008 | 3.409279 | 3.208988 | 3.333806 |
| 10 | 0.05 | Euler-Maruyama | 5.971964 | 5.743228 | 5.894126 | 5.646688 | 3.287663 | 3.406148 | 3.205987 | 3.330861 |
| 11 | 0.05 | Milstein | 5.970964 | 5.742027 | 5.893078 | 5.645582 | 3.290267 | 3.408488 | 3.208263 | 3.333029 |
| 12 | 0.10 | Closed Form | 7.255757 | 6.970206 | 7.194305 | 6.881914 | 2.317638 | 2.403515 | 2.247018 | 2.335700 |
| 13 | 0.10 | Euler-Maruyama | 7.255656 | 6.970164 | 7.193898 | 6.881774 | 2.315086 | 2.400815 | 2.244434 | 2.333144 |
| 14 | 0.10 | Milstein | 7.253449 | 6.968102 | 7.192027 | 6.879858 | 2.316407 | 2.402206 | 2.245820 | 2.334422 |
returns = np.arange(-0.1, 0.11, 0.05)
results = []
for r in returns:
# Generate paths with the current volatility
CF_paths = closed_form(S0, r, sigma, T, t_steps, sims)
CF_paths_df = pd.DataFrame(CF_paths).transpose()
EM_paths = euler_maruyama(S0, r, sigma, T, t_steps, sims)
EM_paths_df = pd.DataFrame(EM_paths).transpose()
MS_paths = milstein_paths(S0, r, sigma, T, t_steps, sims)
MS_paths_df = pd.DataFrame(MS_paths).transpose()
# Calculate payoffs for the current volatility
CF_payoffs = asian_option_param_float(CF_paths_df, 'monthly')
EM_payoffs = asian_option_param_float(EM_paths_df, 'monthly')
MS_payoffs = asian_option_param_float(MS_paths_df, 'monthly')
# Append the results to the list
results.append({"Return": r, "Scheme": "Closed Form", **CF_payoffs})
results.append({"Return": r, "Scheme": "Euler-Maruyama", **EM_payoffs})
results.append({"Return": r, "Scheme": "Milstein", **MS_payoffs})
# Create the results DataFrame
return_results_df = pd.DataFrame(results)
# Format the "Return" column
return_results_df["Return"] = return_results_df["Return"].apply(lambda x: round(x, 2))
# Display the DataFrame
print("Impact of changing return:")
display(return_results_df)
Impact of changing return:
| Return | Scheme | Call_CA | Call_CG | Call_DA | Call_DG | Put_CA | Put_CG | Put_DA | Put_DG | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | -0.10 | Closed Form | 2.749910 | 2.848652 | 2.671094 | 2.774829 | 7.659442 | 7.359476 | 7.588910 | 7.263022 |
| 1 | -0.10 | Euler-Maruyama | 2.749790 | 2.848552 | 2.670972 | 2.774575 | 7.662004 | 7.361734 | 7.591164 | 7.264820 |
| 2 | -0.10 | Milstein | 2.751335 | 2.850167 | 2.672483 | 2.776313 | 7.661727 | 7.361554 | 7.591162 | 7.265050 |
| 3 | -0.05 | Closed Form | 3.651982 | 3.781907 | 3.568536 | 3.707359 | 5.928541 | 5.699112 | 5.848525 | 5.602492 |
| 4 | -0.05 | Euler-Maruyama | 3.652306 | 3.782399 | 3.569059 | 3.707855 | 5.930798 | 5.701356 | 5.850681 | 5.604451 |
| 5 | -0.05 | Milstein | 3.652820 | 3.782801 | 3.569358 | 3.708238 | 5.929572 | 5.700058 | 5.849539 | 5.603414 |
| 6 | 0.00 | Closed Form | 4.720829 | 4.891752 | 4.640412 | 4.823548 | 4.451210 | 4.279375 | 4.372918 | 4.189644 |
| 7 | 0.00 | Euler-Maruyama | 4.720763 | 4.891584 | 4.640476 | 4.823398 | 4.452804 | 4.280814 | 4.374353 | 4.190828 |
| 8 | 0.00 | Milstein | 4.720845 | 4.891767 | 4.640425 | 4.823561 | 4.451227 | 4.279390 | 4.372932 | 4.189657 |
| 9 | 0.05 | Closed Form | 5.986836 | 6.206926 | 5.904629 | 6.142226 | 3.254626 | 3.127422 | 3.176537 | 3.041727 |
| 10 | 0.05 | Euler-Maruyama | 5.985910 | 6.205819 | 5.903487 | 6.140985 | 3.255591 | 3.128279 | 3.177007 | 3.042192 |
| 11 | 0.05 | Milstein | 5.985773 | 6.205782 | 5.903583 | 6.141095 | 3.253873 | 3.126724 | 3.175801 | 3.041051 |
| 12 | 0.10 | Closed Form | 7.420827 | 7.698624 | 7.339389 | 7.643309 | 2.306538 | 2.212907 | 2.234268 | 2.137115 |
| 13 | 0.10 | Euler-Maruyama | 7.418813 | 7.696273 | 7.337676 | 7.641419 | 2.307173 | 2.213412 | 2.234931 | 2.137839 |
| 14 | 0.10 | Milstein | 7.418425 | 7.696026 | 7.337023 | 7.640734 | 2.305281 | 2.211734 | 2.233043 | 2.135983 |
# Set parameters
S0 = 100
r = 0.05
sigma = 0.2
#T = 1
#t_steps = 252
sims = 3000
E = 100
horizon = np.arange(0.5, 3.1, 0.5)
results = []
for T in horizon:
# We defined in the lecture that we can consider continuity with 252 times steps (business day) per year.
# We force an integer value for random number selction
t_steps = math.ceil(252*T)
# Generate paths with the current volatility
CF_paths = closed_form(S0, r, sigma, T, t_steps, sims)
CF_paths_df = pd.DataFrame(CF_paths).transpose()
EM_paths = euler_maruyama(S0, r, sigma, T, t_steps, sims)
EM_paths_df = pd.DataFrame(EM_paths).transpose()
MS_paths = milstein_paths(S0, r, sigma, T, t_steps, sims)
MS_paths_df = pd.DataFrame(MS_paths).transpose()
# Calculate payoffs for the current volatility
CF_payoffs = asian_option_payoffs_param(CF_paths_df, E, 'monthly')
EM_payoffs = asian_option_payoffs_param(EM_paths_df, E, 'monthly')
MS_payoffs = asian_option_payoffs_param(MS_paths_df, E, 'monthly')
# Append the results to the list
results.append({"Horizon": T, "Scheme": "Closed Form", **CF_payoffs})
results.append({"Horizon": T, "Scheme": "Euler-Maruyama", **EM_payoffs})
results.append({"Horizon": T, "Scheme": "Milstein", **MS_payoffs})
# Create the results DataFrame
T_results_df = pd.DataFrame(results)
# Display the DataFrame
print("Impact of changing time horizon:")
display(T_results_df)
Impact of changing time horizon:
| Horizon | Scheme | Call_CA | Call_CG | Call_DA | Call_DG | Put_CA | Put_CG | Put_DA | Put_DG | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0.5 | Closed Form | 4.038655 | 3.931050 | 3.921397 | 3.798712 | 2.616748 | 2.683561 | 2.512477 | 2.587788 |
| 1 | 0.5 | Euler-Maruyama | 4.038364 | 3.931034 | 3.921259 | 3.798670 | 2.615816 | 2.682869 | 2.511815 | 2.587164 |
| 2 | 0.5 | Milstein | 4.037922 | 3.930358 | 3.920685 | 3.798047 | 2.616186 | 2.682972 | 2.511934 | 2.587215 |
| 3 | 1.0 | Closed Form | 5.972005 | 5.742982 | 5.894104 | 5.646514 | 3.291008 | 3.409279 | 3.208988 | 3.333806 |
| 4 | 1.0 | Euler-Maruyama | 5.971964 | 5.743228 | 5.894126 | 5.646688 | 3.287663 | 3.406148 | 3.205987 | 3.330861 |
| 5 | 1.0 | Milstein | 5.970964 | 5.742027 | 5.893078 | 5.645582 | 3.290267 | 3.408488 | 3.208263 | 3.333029 |
| 6 | 1.5 | Closed Form | 7.360125 | 7.007424 | 7.301167 | 6.928878 | 3.659060 | 3.820438 | 3.596832 | 3.763554 |
| 7 | 1.5 | Euler-Maruyama | 7.361107 | 7.008432 | 7.302391 | 6.930424 | 3.657128 | 3.818338 | 3.595252 | 3.762093 |
| 8 | 1.5 | Milstein | 7.358875 | 7.006304 | 7.299934 | 6.927779 | 3.658187 | 3.819495 | 3.595976 | 3.762623 |
| 9 | 2.0 | Closed Form | 8.679627 | 8.198243 | 8.628902 | 8.126528 | 4.055962 | 4.252549 | 3.999215 | 4.201207 |
| 10 | 2.0 | Euler-Maruyama | 8.682744 | 8.201341 | 8.631843 | 8.129543 | 4.054443 | 4.251182 | 3.997680 | 4.199904 |
| 11 | 2.0 | Milstein | 8.678171 | 8.196964 | 8.627458 | 8.125268 | 4.054970 | 4.251471 | 3.998235 | 4.200139 |
| 12 | 2.5 | Closed Form | 9.799946 | 9.185370 | 9.766331 | 9.130055 | 4.044069 | 4.280286 | 4.002612 | 4.243991 |
| 13 | 2.5 | Euler-Maruyama | 9.804486 | 9.189675 | 9.770726 | 9.134522 | 4.045762 | 4.281887 | 4.004179 | 4.245775 |
| 14 | 2.5 | Milstein | 9.798363 | 9.184008 | 9.764750 | 9.128706 | 4.043052 | 4.279165 | 4.001601 | 4.242874 |
| 15 | 3.0 | Closed Form | 10.960915 | 10.222550 | 10.930923 | 10.168882 | 4.394426 | 4.658441 | 4.351662 | 4.619696 |
| 16 | 3.0 | Euler-Maruyama | 10.959922 | 10.221459 | 10.930092 | 10.167777 | 4.390681 | 4.654629 | 4.348189 | 4.615971 |
| 17 | 3.0 | Milstein | 10.959161 | 10.221067 | 10.929178 | 10.167414 | 4.393310 | 4.657214 | 4.350557 | 4.618476 |
horizon = np.arange(0.5, 3.1, 0.5)
results = []
for T in horizon:
# We defined in the lecture that we can consider continuity with 252 times steps (business day) per year
t_steps = math.ceil(252*T)
# Generate paths with the current volatility
CF_paths = closed_form(S0, r, sigma, T, t_steps, sims)
CF_paths_df = pd.DataFrame(CF_paths).transpose()
EM_paths = euler_maruyama(S0, r, sigma, T, t_steps, sims)
EM_paths_df = pd.DataFrame(EM_paths).transpose()
MS_paths = milstein_paths(S0, r, sigma, T, t_steps, sims)
MS_paths_df = pd.DataFrame(MS_paths).transpose()
# Calculate payoffs for the current volatility
CF_payoffs = asian_option_param_float(CF_paths_df, 'monthly')
EM_payoffs = asian_option_param_float(EM_paths_df, 'monthly')
MS_payoffs = asian_option_param_float(MS_paths_df, 'monthly')
# Append the results to the list
results.append({"Horizon": T, "Scheme": "Closed Form", **CF_payoffs})
results.append({"Horizon": T, "Scheme": "Euler-Maruyama", **EM_payoffs})
results.append({"Horizon": T, "Scheme": "Milstein", **MS_payoffs})
# Create the results DataFrame
T_results_df = pd.DataFrame(results)
# Display the DataFrame
print("Impact of changing return:")
display(T_results_df)
Impact of changing return:
| Horizon | Scheme | Call_CA | Call_CG | Call_DA | Call_DG | Put_CA | Put_CG | Put_DA | Put_DG | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0.5 | Closed Form | 3.939561 | 4.044197 | 3.839106 | 3.959055 | 2.629035 | 2.559253 | 2.515593 | 2.437546 |
| 1 | 0.5 | Euler-Maruyama | 3.940138 | 4.044692 | 3.840010 | 3.959788 | 2.629671 | 2.559843 | 2.516439 | 2.438279 |
| 2 | 0.5 | Milstein | 3.938847 | 4.043444 | 3.838412 | 3.958317 | 2.628468 | 2.558714 | 2.515048 | 2.437034 |
| 3 | 1.0 | Closed Form | 5.986836 | 6.206926 | 5.904629 | 6.142226 | 3.254626 | 3.127422 | 3.176537 | 3.041727 |
| 4 | 1.0 | Euler-Maruyama | 5.985910 | 6.205819 | 5.903487 | 6.140985 | 3.255591 | 3.128279 | 3.177007 | 3.042192 |
| 5 | 1.0 | Milstein | 5.985773 | 6.205782 | 5.903583 | 6.141095 | 3.253873 | 3.126724 | 3.175801 | 3.041051 |
| 6 | 1.5 | Closed Form | 7.601809 | 7.943905 | 7.528586 | 7.889440 | 3.698014 | 3.526031 | 3.628062 | 3.449905 |
| 7 | 1.5 | Euler-Maruyama | 7.603452 | 7.945583 | 7.530389 | 7.890972 | 3.697779 | 3.526025 | 3.627878 | 3.449652 |
| 8 | 1.5 | Milstein | 7.600520 | 7.942490 | 7.527312 | 7.888036 | 3.697140 | 3.525230 | 3.627203 | 3.449123 |
| 9 | 2.0 | Closed Form | 9.142393 | 9.606169 | 9.079967 | 9.564216 | 4.043784 | 3.829589 | 3.987382 | 3.767265 |
| 10 | 2.0 | Euler-Maruyama | 9.143821 | 9.607642 | 9.081445 | 9.565576 | 4.044200 | 3.829878 | 3.987685 | 3.767293 |
| 11 | 2.0 | Milstein | 9.140846 | 9.604451 | 9.078432 | 9.562504 | 4.042790 | 3.828687 | 3.986398 | 3.766377 |
| 12 | 2.5 | Closed Form | 10.678234 | 11.258073 | 10.615689 | 11.218182 | 4.530128 | 4.259173 | 4.475424 | 4.200262 |
| 13 | 2.5 | Euler-Maruyama | 10.681513 | 11.261601 | 10.619217 | 11.221741 | 4.528682 | 4.257833 | 4.474208 | 4.198933 |
| 14 | 2.5 | Milstein | 10.676415 | 11.256048 | 10.613882 | 11.216164 | 4.528996 | 4.258162 | 4.474302 | 4.199266 |
| 15 | 3.0 | Closed Form | 11.759474 | 12.469183 | 11.704167 | 12.437640 | 4.482146 | 4.189474 | 4.439611 | 4.143008 |
| 16 | 3.0 | Euler-Maruyama | 11.761892 | 12.471052 | 11.706463 | 12.439490 | 4.483304 | 4.190052 | 4.440537 | 4.143467 |
| 17 | 3.0 | Milstein | 11.757494 | 12.466963 | 11.702204 | 12.435425 | 4.480962 | 4.188434 | 4.438442 | 4.141981 |
#S0 = 100 # Today's stock price
T = 1 # Time (time to expiry)
sigma = 0.2 # Volatility
r = 0.05 # Constant risk-free interest rate (= mu in case of risk neutrality)
E = 100 # Strike
sims = 3000
start_prices = np.arange(85, 116, 5)
results = []
for S0 in start_prices:
# Generate paths with the current volatility
CF_paths = closed_form(S0, r, sigma, T, t_steps, sims)
CF_paths_df = pd.DataFrame(CF_paths).transpose()
EM_paths = euler_maruyama(S0, r, sigma, T, t_steps, sims)
EM_paths_df = pd.DataFrame(EM_paths).transpose()
MS_paths = milstein_paths(S0, r, sigma, T, t_steps, sims)
MS_paths_df = pd.DataFrame(MS_paths).transpose()
# Calculate payoffs for the current volatility
CF_payoffs = asian_option_payoffs_param(CF_paths_df, E, 'monthly')
EM_payoffs = asian_option_payoffs_param(EM_paths_df, E, 'monthly')
MS_payoffs = asian_option_payoffs_param(MS_paths_df, E, 'monthly')
# Append the results to the list
results.append({"S0": S0, "Scheme": "Closed Form", **CF_payoffs})
results.append({"S0": S0, "Scheme": "Euler-Maruyama", **EM_payoffs})
results.append({"S0": S0, "Scheme": "Milstein", **MS_payoffs})
# Create the results DataFrame
ITM_OTM_results_df = pd.DataFrame(results)
# Display the DataFrame
print("Impact of changing starting price:")
display(ITM_OTM_results_df)
Impact of changing starting price:
| S0 | Scheme | Call_CA | Call_CG | Call_DA | Call_DG | Put_CA | Put_CG | Put_DA | Put_DG | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 85 | Closed Form | 0.584502 | 0.514149 | 0.575525 | 0.502857 | 12.909240 | 13.121523 | 12.895664 | 13.112951 |
| 1 | 85 | Euler-Maruyama | 0.584154 | 0.513706 | 0.575110 | 0.502313 | 12.907928 | 13.120134 | 12.894317 | 13.111493 |
| 2 | 85 | Milstein | 0.584363 | 0.514029 | 0.575388 | 0.502739 | 12.909160 | 13.121425 | 12.895585 | 13.112854 |
| 3 | 90 | Closed Form | 1.542434 | 1.418497 | 1.525848 | 1.398000 | 8.996689 | 9.172015 | 8.975234 | 9.154397 |
| 4 | 90 | Euler-Maruyama | 1.542117 | 1.418280 | 1.525616 | 1.397756 | 8.995351 | 9.170796 | 8.974015 | 9.153185 |
| 5 | 90 | Milstein | 1.542195 | 1.418279 | 1.525612 | 1.397783 | 8.996512 | 9.171819 | 8.975059 | 9.154202 |
| 6 | 95 | Closed Form | 3.186994 | 3.017563 | 3.165271 | 2.990116 | 5.770767 | 5.917223 | 5.743903 | 5.892816 |
| 7 | 95 | Euler-Maruyama | 3.186554 | 3.017302 | 3.164954 | 2.989921 | 5.769248 | 5.915905 | 5.742546 | 5.891600 |
| 8 | 95 | Milstein | 3.186688 | 3.017280 | 3.164966 | 2.989836 | 5.770526 | 5.916964 | 5.743664 | 5.892559 |
| 9 | 100 | Closed Form | 5.693648 | 5.479552 | 5.668779 | 5.448496 | 3.406939 | 3.525355 | 3.376658 | 3.497499 |
| 10 | 100 | Euler-Maruyama | 5.693276 | 5.479226 | 5.668292 | 5.448135 | 3.405432 | 3.523916 | 3.375075 | 3.496062 |
| 11 | 100 | Milstein | 5.693325 | 5.479255 | 5.668457 | 5.448201 | 3.406684 | 3.525083 | 3.376405 | 3.497228 |
| 12 | 105 | Closed Form | 8.993580 | 8.737693 | 8.973052 | 8.710542 | 1.836388 | 1.929640 | 1.810178 | 1.905848 |
| 13 | 105 | Euler-Maruyama | 8.994289 | 8.738548 | 8.973791 | 8.711428 | 1.835905 | 1.929326 | 1.809767 | 1.905605 |
| 14 | 105 | Milstein | 8.993279 | 8.737422 | 8.972752 | 8.710273 | 1.836158 | 1.929395 | 1.809950 | 1.905604 |
| 15 | 110 | Closed Form | 12.903636 | 12.606241 | 12.890038 | 12.584898 | 0.875961 | 0.944330 | 0.856411 | 0.926507 |
| 16 | 110 | Euler-Maruyama | 12.905194 | 12.607806 | 12.891531 | 12.586457 | 0.876270 | 0.944672 | 0.856699 | 0.926883 |
| 17 | 110 | Milstein | 12.903391 | 12.606027 | 12.889796 | 12.584689 | 0.875791 | 0.944144 | 0.856244 | 0.926325 |
| 18 | 115 | Closed Form | 17.275001 | 16.936247 | 17.269034 | 16.921975 | 0.376844 | 0.420480 | 0.364654 | 0.409887 |
| 19 | 115 | Euler-Maruyama | 17.276950 | 16.938200 | 17.271045 | 16.923872 | 0.377488 | 0.421153 | 0.365404 | 0.410547 |
| 20 | 115 | Milstein | 17.274819 | 16.936105 | 17.268855 | 16.921834 | 0.376740 | 0.420366 | 0.364554 | 0.409774 |
start_prices = np.arange(85, 116, 5)
results = []
for S0 in start_prices:
# Generate paths with the current volatility
CF_paths = closed_form(S0, r, sigma, T, t_steps, sims)
CF_paths_df = pd.DataFrame(CF_paths).transpose()
EM_paths = euler_maruyama(S0, r, sigma, T, t_steps, sims)
EM_paths_df = pd.DataFrame(EM_paths).transpose()
MS_paths = milstein_paths(S0, r, sigma, T, t_steps, sims)
MS_paths_df = pd.DataFrame(MS_paths).transpose()
# Calculate payoffs for the current volatility
CF_payoffs = asian_option_param_float(CF_paths_df, 'monthly')
EM_payoffs = asian_option_param_float(EM_paths_df, 'monthly')
MS_payoffs = asian_option_param_float(MS_paths_df, 'monthly')
# Append the results to the list
results.append({"S0": S0, "Scheme": "Closed Form", **CF_payoffs})
results.append({"S0": S0, "Scheme": "Euler-Maruyama", **EM_payoffs})
results.append({"S0": S0, "Scheme": "Milstein", **MS_payoffs})
# Create the results DataFrame
ITM_OTM_results_df = pd.DataFrame(results)
# Display the DataFrame
print("Impact of changing starting stock prices:")
display(ITM_OTM_results_df)
Impact of changing starting stock prices:
| S0 | Scheme | Call_CA | Call_CG | Call_DA | Call_DG | Put_CA | Put_CG | Put_DA | Put_DG | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 85 | Closed Form | 5.013361 | 5.188780 | 4.986555 | 5.167597 | 2.857998 | 2.750780 | 2.835791 | 2.726877 |
| 1 | 85 | Euler-Maruyama | 5.014207 | 5.189499 | 4.987353 | 5.168143 | 2.858465 | 2.751101 | 2.836176 | 2.726994 |
| 2 | 85 | Milstein | 5.013066 | 5.188464 | 4.986262 | 5.167281 | 2.857776 | 2.750574 | 2.835571 | 2.726673 |
| 3 | 90 | Closed Form | 5.308265 | 5.494002 | 5.279882 | 5.471573 | 3.026115 | 2.912591 | 3.002602 | 2.887282 |
| 4 | 90 | Euler-Maruyama | 5.309161 | 5.494763 | 5.280726 | 5.472151 | 3.026610 | 2.912931 | 3.003010 | 2.887405 |
| 5 | 90 | Milstein | 5.307952 | 5.493668 | 5.279571 | 5.471239 | 3.025880 | 2.912373 | 3.002369 | 2.887066 |
| 6 | 95 | Closed Form | 5.603169 | 5.799225 | 5.573208 | 5.775549 | 3.194233 | 3.074401 | 3.169413 | 3.047687 |
| 7 | 95 | Euler-Maruyama | 5.604114 | 5.800028 | 5.574100 | 5.776160 | 3.194755 | 3.074760 | 3.169844 | 3.047817 |
| 8 | 95 | Milstein | 5.602838 | 5.798871 | 5.572881 | 5.775197 | 3.193985 | 3.074171 | 3.169167 | 3.047458 |
| 9 | 100 | Closed Form | 5.898072 | 6.104447 | 5.866535 | 6.079526 | 3.362350 | 3.236212 | 3.336224 | 3.208091 |
| 10 | 100 | Euler-Maruyama | 5.899067 | 6.105293 | 5.867474 | 6.080168 | 3.362900 | 3.236590 | 3.336678 | 3.208228 |
| 11 | 100 | Milstein | 5.897724 | 6.104075 | 5.866190 | 6.079154 | 3.362089 | 3.235970 | 3.335966 | 3.207851 |
| 12 | 105 | Closed Form | 6.192976 | 6.409670 | 6.159862 | 6.383502 | 3.530468 | 3.398023 | 3.503035 | 3.368496 |
| 13 | 105 | Euler-Maruyama | 6.194021 | 6.410557 | 6.160848 | 6.384176 | 3.531045 | 3.398419 | 3.503512 | 3.368639 |
| 14 | 105 | Milstein | 6.192611 | 6.409279 | 6.159500 | 6.383112 | 3.530194 | 3.397768 | 3.502764 | 3.368243 |
| 15 | 110 | Closed Form | 6.487879 | 6.714892 | 6.453189 | 6.687478 | 3.698585 | 3.559833 | 3.669847 | 3.528900 |
| 16 | 110 | Euler-Maruyama | 6.488974 | 6.715822 | 6.454221 | 6.688185 | 3.699190 | 3.560249 | 3.670346 | 3.529051 |
| 17 | 110 | Milstein | 6.487497 | 6.714483 | 6.452809 | 6.687070 | 3.698298 | 3.559567 | 3.669562 | 3.528636 |
| 18 | 115 | Closed Form | 6.782783 | 7.020114 | 6.746515 | 6.991454 | 3.866703 | 3.721644 | 3.836658 | 3.689305 |
| 19 | 115 | Euler-Maruyama | 6.783927 | 7.021086 | 6.747595 | 6.992193 | 3.867335 | 3.722078 | 3.837180 | 3.689462 |
| 20 | 115 | Milstein | 6.782383 | 7.019686 | 6.746119 | 6.991028 | 3.866403 | 3.721365 | 3.836360 | 3.689028 |
# Define the frequency of periods
freq_periods = {
'daily': 1,
'weekly': 5,
'biweekly': 10,
'monthly': 21,
'quarterly': 63,
'semester': 126
}
# Inital assignment of parameters based on values provided in the exam
S0 = 100 # Today's stock price
T = 1 # Time (time to expiry)
sigma = 0.2 # Volatility
r = 0.05 # Constant risk-free interest rate (= mu in case of risk neutrality)
E = 100 # Strike
sims = 1
# We define the number of time steps based on number of working days in a year (as per Tutorial)
t_steps = 252
def create_plots(chosen_frequency):
periods = freq_periods[chosen_frequency]
# Run simulations
CF_path = closed_form(S0, r, sigma, T, t_steps, sims)
# Convert the lists of paths into DataFrames
CF_path_df = pd.DataFrame(CF_path).transpose()
# Calculate averages using the modified functions
DA_CF_df = discrete_arithmetic_avrg(CF_path_df, periods)
DG_CF_df = discrete_geometric_avrg(CF_path_df, periods)
# Calculate the discrete average value by taking the mean of all the averages
DA_CF_mean = DA_CF_df.mean()[0]
DG_CF_mean = DG_CF_df.mean()[0]
fig1 = go.Figure() # Discrete Averages Plot
# Stock price
fig1.add_trace(go.Scatter(x=list(range(t_steps)), y=CF_path[0], mode='lines', name='CF_price'))
# Discrete plot
discrete_path = discrete_stock_path(CF_path_df[0], periods)
fig1.add_trace(go.Scatter(x=list(range(t_steps)), y=discrete_path, mode='lines',
name='Discrete_path', line=dict(color='purple')))
# Closed-Form
fig1.add_trace(go.Scatter(x=list(range(t_steps)), y=DA_CF_df[0], mode='lines', name='DA'))
fig1.add_trace(go.Scatter(x=list(range(t_steps)), y=DG_CF_df[0], mode='lines', name='DG'))
# Add horizontal dashed lines for the discrete mean
fig1.add_shape(type='line', x0=0, x1=t_steps-1, y0=DA_CF_mean, y1=DA_CF_mean,
yref='y', xref='x', line=dict(color='red', dash='dash'))
fig1.add_shape(type='line', x0=0, x1=t_steps-1, y0=DG_CF_mean, y1=DG_CF_mean,
yref='y', xref='x', line=dict(color='green', dash='dash'))
# Add legend entries for the dashed lines
fig1.add_trace(go.Scatter(x=[None], y=[None], mode='lines', line=dict(color='red', dash='dash'),
name='Discrete Arithmetic Mean'))
fig1.add_trace(go.Scatter(x=[None], y=[None], mode='lines', line=dict(color='green', dash='dash'),
name='Discrete Geometric Mean'))
fig1.update_layout(title='Discrete Averages', xaxis_title='Time steps', yaxis_title='Stock Price')
fig1.show()
create_plots('daily')
create_plots('weekly')
create_plots('biweekly')
create_plots('monthly')
create_plots('quarterly')
create_plots('semester')
sims = 3000
# Generate paths and measure time
CF_paths = closed_form(S0, r, sigma, T, t_steps, sims)
EM_paths = euler_maruyama(S0, r, sigma, T, t_steps, sims)
MS_paths = milstein_paths(S0, r, sigma, T, t_steps, sims)
# Convert the lists of paths into DataFrames
CF_paths_df = pd.DataFrame(CF_paths).transpose()
EM_paths_df = pd.DataFrame(EM_paths).transpose()
MS_paths_df = pd.DataFrame(MS_paths).transpose()
# Calculate payoffs and measure time for daily frequency
CF_payoffs_daily, CF_avg_times_daily, CF_payoffs_time_daily = measure_time(asian_option_payoffs, CF_paths_df, E, 'daily')
EM_payoffs_daily, EM_avg_times_daily, EM_payoffs_time_daily = measure_time(asian_option_payoffs, EM_paths_df, E, 'daily')
MS_payoffs_daily, MS_avg_times_daily, MS_payoffs_time_daily = measure_time(asian_option_payoffs, MS_paths_df, E, 'daily')
# Calculate payoffs and measure time for weekly frequency
CF_payoffs_weekly, CF_avg_times_weekly, CF_payoffs_time_weekly = measure_time(asian_option_payoffs, CF_paths_df, E, 'weekly')
EM_payoffs_weekly, EM_avg_times_weekly, EM_payoffs_time_weekly = measure_time(asian_option_payoffs, EM_paths_df, E, 'weekly')
MS_payoffs_weekly, MS_avg_times_weekly, MS_payoffs_time_weekly = measure_time(asian_option_payoffs, MS_paths_df, E, 'weekly')
# Calculate payoffs and measure time for biweekly frequency
CF_payoffs_biweekly, CF_avg_times_biweekly, CF_payoffs_time_biweekly = measure_time(asian_option_payoffs, CF_paths_df, E, 'biweekly')
EM_payoffs_biweekly, EM_avg_times_biweekly, EM_payoffs_time_biweekly = measure_time(asian_option_payoffs, EM_paths_df, E, 'biweekly')
MS_payoffs_biweekly, MS_avg_times_biweekly, MS_payoffs_time_biweekly = measure_time(asian_option_payoffs, MS_paths_df, E, 'biweekly')
# Calculate payoffs and measure time for monthly frequency
CF_payoffs_monthly, CF_avg_times_monthly, CF_payoffs_time_monthly = measure_time(asian_option_payoffs, CF_paths_df, E, 'monthly')
EM_payoffs_monthly, EM_avg_times_monthly, EM_payoffs_time_monthly = measure_time(asian_option_payoffs, EM_paths_df, E, 'monthly')
MS_payoffs_monthly, MS_avg_times_monthly, MS_payoffs_time_monthly = measure_time(asian_option_payoffs, MS_paths_df, E, 'monthly')
# Calculate payoffs and measure time for quarterly frequency
CF_payoffs_quarterly, CF_avg_times_quarterly, CF_payoffs_time_quarterly = measure_time(asian_option_payoffs, CF_paths_df, E, 'quarterly')
EM_payoffs_quarterly, EM_avg_times_quarterly, EM_payoffs_time_quarterly = measure_time(asian_option_payoffs, EM_paths_df, E, 'quarterly')
MS_payoffs_quarterly, MS_avg_times_quarterly, MS_payoffs_time_quarterly = measure_time(asian_option_payoffs, MS_paths_df, E, 'quarterly')
# Calculate payoffs and measure time for semester frequency
CF_payoffs_semester, CF_avg_times_semester, CF_payoffs_time_semester = measure_time(asian_option_payoffs, CF_paths_df, E, 'semester')
EM_payoffs_semester, EM_avg_times_semester, EM_payoffs_time_semester = measure_time(asian_option_payoffs, EM_paths_df, E, 'semester')
MS_payoffs_semester, MS_avg_times_semester, MS_payoffs_time_semester = measure_time(asian_option_payoffs, MS_paths_df, E, 'semester')
# Combine the payoffs into a DataFrame
payoffs_df = pd.DataFrame({'Closed Form Daily': CF_payoffs_daily,
'Euler-Maruyama Daily': EM_payoffs_daily,
'Milstein Daily': MS_payoffs_daily,
'Closed Form Weekly': CF_payoffs_weekly,
'Euler-Maruyama Weekly': EM_payoffs_weekly,
'Milstein Weekly': MS_payoffs_weekly,
'Closed Form Biweekly': CF_payoffs_biweekly,
'Euler-Maruyama Biweekly': EM_payoffs_biweekly,
'Milstein Biweekly': MS_payoffs_biweekly,
'Closed Form Monthly': CF_payoffs_monthly,
'Euler-Maruyama Monthly': EM_payoffs_monthly,
'Milstein Monthly': MS_payoffs_monthly,
'Closed Form Quarterly': CF_payoffs_quarterly,
'Euler-Maruyama Quarterly': EM_payoffs_quarterly,
'Milstein Quarterly': MS_payoffs_quarterly,
'Closed Form Semester': CF_payoffs_semester,
'Euler-Maruyama Semester': EM_payoffs_semester,
'Milstein Semester': MS_payoffs_semester
}).transpose()
# Display the table
payoffs_df
| Call_CA | Call_CG | Call_DA | Call_DG | Put_CA | Put_CG | Put_DA | Put_DG | |
|---|---|---|---|---|---|---|---|---|
| Closed Form Daily | 5.972005 | 5.742982 | 5.972005 | 5.742982 | 3.291008 | 3.409279 | 3.291008 | 3.409279 |
| Euler-Maruyama Daily | 5.971964 | 5.743228 | 5.971964 | 5.743228 | 3.287663 | 3.406148 | 3.287663 | 3.406148 |
| Milstein Daily | 5.970964 | 5.742027 | 5.970964 | 5.742027 | 3.290267 | 3.408488 | 3.290267 | 3.408488 |
| Closed Form Weekly | 5.972005 | 5.742982 | 5.927650 | 5.696655 | 3.291008 | 3.409279 | 3.263791 | 3.382654 |
| Euler-Maruyama Weekly | 5.971964 | 5.743228 | 5.927628 | 5.696950 | 3.287663 | 3.406148 | 3.260462 | 3.379565 |
| Milstein Weekly | 5.970964 | 5.742027 | 5.926616 | 5.695709 | 3.290267 | 3.408488 | 3.263056 | 3.381869 |
| Closed Form Biweekly | 5.972005 | 5.742982 | 5.905184 | 5.669475 | 3.291008 | 3.409279 | 3.241841 | 3.362443 |
| Euler-Maruyama Biweekly | 5.971964 | 5.743228 | 5.905397 | 5.669749 | 3.287663 | 3.406148 | 3.238750 | 3.359332 |
| Milstein Biweekly | 5.970964 | 5.742027 | 5.904156 | 5.668536 | 3.290267 | 3.408488 | 3.241111 | 3.361663 |
| Closed Form Monthly | 5.972005 | 5.742982 | 5.894104 | 5.646514 | 3.291008 | 3.409279 | 3.208988 | 3.333806 |
| Euler-Maruyama Monthly | 5.971964 | 5.743228 | 5.894126 | 5.646688 | 3.287663 | 3.406148 | 3.205987 | 3.330861 |
| Milstein Monthly | 5.970964 | 5.742027 | 5.893078 | 5.645582 | 3.290267 | 3.408488 | 3.208263 | 3.333029 |
| Closed Form Quarterly | 5.972005 | 5.742982 | 5.752753 | 5.473459 | 3.291008 | 3.409279 | 3.056623 | 3.193749 |
| Euler-Maruyama Quarterly | 5.971964 | 5.743228 | 5.752851 | 5.473910 | 3.287663 | 3.406148 | 3.054169 | 3.191528 |
| Milstein Quarterly | 5.970964 | 5.742027 | 5.751754 | 5.472564 | 3.290267 | 3.408488 | 3.055927 | 3.192995 |
| Closed Form Semester | 5.972005 | 5.742982 | 5.604054 | 5.285136 | 3.291008 | 3.409279 | 2.926096 | 3.074052 |
| Euler-Maruyama Semester | 5.971964 | 5.743228 | 5.603883 | 5.285251 | 3.287663 | 3.406148 | 2.923868 | 3.071927 |
| Milstein Semester | 5.970964 | 5.742027 | 5.603088 | 5.284287 | 3.290267 | 3.408488 | 2.925430 | 3.073322 |
# Create dictionary for each frequency
daily_times_DG = {"Closed Form Daily": CF_avg_times_daily["DG_time"],
"Euler-Maruyama Daily": EM_avg_times_daily["DG_time"],
"Milstein Daily": MS_avg_times_daily["DG_time"]}
daily_times_DA = {"Closed Form Daily": CF_avg_times_daily["DA_time"],
"Euler-Maruyama Daily": EM_avg_times_daily["DA_time"],
"Milstein Daily": MS_avg_times_daily["DA_time"]}
# Weekly
weekly_times_DG = {"Closed Form Weekly": CF_avg_times_weekly["DG_time"],
"Euler-Maruyama Weekly": EM_avg_times_weekly["DG_time"],
"Milstein Weekly": MS_avg_times_weekly["DG_time"]}
weekly_times_DA = {"Closed Form Weekly": CF_avg_times_weekly["DA_time"],
"Euler-Maruyama Weekly": EM_avg_times_weekly["DA_time"],
"Milstein Weekly": MS_avg_times_weekly["DA_time"]}
# Biweekly
biweekly_times_DG = {"Closed Form Biweekly": CF_avg_times_biweekly["DG_time"],
"Euler-Maruyama Biweekly": EM_avg_times_biweekly["DG_time"],
"Milstein Biweekly": MS_avg_times_biweekly["DG_time"]}
biweekly_times_DA = {"Closed Form Biweekly": CF_avg_times_biweekly["DA_time"],
"Euler-Maruyama Biweekly": EM_avg_times_biweekly["DA_time"],
"Milstein Biweekly": MS_avg_times_biweekly["DA_time"]}
# Monthly
monthly_times_DG = {"Closed Form Monthly": CF_avg_times_monthly["DG_time"],
"Euler-Maruyama Monthly": EM_avg_times_monthly["DG_time"],
"Milstein Monthly": MS_avg_times_monthly["DG_time"]}
monthly_times_DA = {"Closed Form Monthly": CF_avg_times_monthly["DA_time"],
"Euler-Maruyama Monthly": EM_avg_times_monthly["DA_time"],
"Milstein Monthly": MS_avg_times_monthly["DA_time"]}
# Quarterly
quarterly_times_DG = {"Closed Form Quarterly": CF_avg_times_quarterly["DG_time"],
"Euler-Maruyama Quarterly": EM_avg_times_quarterly["DG_time"],
"Milstein Quarterly": MS_avg_times_quarterly["DG_time"]}
quarterly_times_DA = {"Closed Form Quarterly": CF_avg_times_quarterly["DA_time"],
"Euler-Maruyama Quarterly": EM_avg_times_quarterly["DA_time"],
"Milstein Quarterly": MS_avg_times_quarterly["DA_time"]}
# Semester
semester_times_DG = {"Closed Form Semester": CF_avg_times_semester["DG_time"],
"Euler-Maruyama Semester": EM_avg_times_semester["DG_time"],
"Milstein Semester": MS_avg_times_semester["DG_time"]}
semester_times_DA = {"Closed Form Semester": CF_avg_times_semester["DA_time"],
"Euler-Maruyama Semester": EM_avg_times_semester["DA_time"],
"Milstein Semester": MS_avg_times_semester["DA_time"]}
# Combine dictionaries
all_times_DG = {**daily_times_DG, **weekly_times_DG, **biweekly_times_DG,
**monthly_times_DG, **quarterly_times_DG, **semester_times_DG}
all_times_DA = {**daily_times_DA, **weekly_times_DA, **biweekly_times_DA,
**monthly_times_DA, **quarterly_times_DA, **semester_times_DA}
# Create DataFrame from dictionaries
running_times_df = pd.DataFrame({"Payoff DG": all_times_DG, "Payoff DA": all_times_DA})
# Display computation time
running_times_df
| Payoff DG | Payoff DA | |
|---|---|---|
| Closed Form Daily | 9.764829 | 9.884471 |
| Euler-Maruyama Daily | 9.875819 | 9.895697 |
| Milstein Daily | 9.991761 | 10.948786 |
| Closed Form Weekly | 2.913063 | 2.628983 |
| Euler-Maruyama Weekly | 2.765884 | 2.615262 |
| Milstein Weekly | 2.560906 | 2.598423 |
| Closed Form Biweekly | 1.580553 | 1.555410 |
| Euler-Maruyama Biweekly | 1.540514 | 1.475310 |
| Milstein Biweekly | 1.594928 | 1.704879 |
| Closed Form Monthly | 1.006115 | 0.977967 |
| Euler-Maruyama Monthly | 1.000391 | 0.995500 |
| Milstein Monthly | 0.981673 | 0.972562 |
| Closed Form Quarterly | 0.723650 | 0.678224 |
| Euler-Maruyama Quarterly | 0.683528 | 0.638352 |
| Milstein Quarterly | 0.624613 | 0.806233 |
| Closed Form Semester | 0.532741 | 0.533636 |
| Euler-Maruyama Semester | 0.531425 | 0.526989 |
| Milstein Semester | 0.528666 | 0.524930 |
def run_simulation(sims):
# Generate paths and measure time
start_time_CF = time.time()
closed_form_paths = closed_form(S0, r, sigma, T, t_steps, sims)
CF_time = time.time() - start_time_CF
start_time_EM = time.time()
EM_paths = euler_maruyama(S0, r, sigma, T, t_steps, sims)
EM_time = time.time() - start_time_EM
start_time_MS = time.time()
MS_paths = milstein_paths(S0, r, sigma, T, t_steps, sims)
MS_time = time.time() - start_time_MS
# Convert the lists of paths into DataFrames
CF_paths_df = pd.DataFrame(closed_form_paths).transpose()
EM_paths_df = pd.DataFrame(EM_paths).transpose()
MS_paths_df = pd.DataFrame(MS_paths).transpose()
# Calculate payoffs and measure time
CF_payoffs, _, _ = measure_time(asian_option_payoffs, CF_paths_df, E, 'monthly')
EM_payoffs, _, _ = measure_time(asian_option_payoffs, EM_paths_df, E, 'monthly')
MS_payoffs, _, _ = measure_time(asian_option_payoffs, MS_paths_df, E, 'monthly')
return {
'Closed Form': {'Call': CF_payoffs['Call_CG'], 'Put': CF_payoffs['Put_CG']},
'Euler-Maruyama': {'Call': EM_payoffs['Call_CG'], 'Put': EM_payoffs['Put_CG']},
'Milstein': {'Call': MS_payoffs['Call_CG'], 'Put': MS_payoffs['Put_CG']}
}
# Run the loop with increasing number of simulations
simulations = list(range(1000, 31001, 2000))
results_call = []
results_put = []
for sims in simulations:
result = run_simulation(sims)
results_call.append({'Simulations': sims,
'Closed Form': result['Closed Form']['Call'],
'Euler-Maruyama': result['Euler-Maruyama']['Call'],
'Milstein': result['Milstein']['Call']})
results_put.append({'Simulations': sims,
'Closed Form': result['Closed Form']['Put'],
'Euler-Maruyama': result['Euler-Maruyama']['Put'],
'Milstein': result['Milstein']['Put']})
# Create the results DataFrames
call_results_df = pd.DataFrame(results_call)
put_results_df = pd.DataFrame(results_put)
# Display the call options table
print("Call Options:")
display(call_results_df)
# Display the put options table
print("\nPut Options:")
display(put_results_df)
Call Options:
| Simulations | Closed Form | Euler-Maruyama | Milstein | |
|---|---|---|---|---|
| 0 | 1000 | 5.935308 | 5.936440 | 5.934297 |
| 1 | 3000 | 5.742982 | 5.743228 | 5.742027 |
| 2 | 5000 | 5.543706 | 5.544363 | 5.542789 |
| 3 | 7000 | 5.510057 | 5.510205 | 5.509146 |
| 4 | 9000 | 5.517675 | 5.516971 | 5.516762 |
| 5 | 11000 | 5.531744 | 5.530984 | 5.530825 |
| 6 | 13000 | 5.536710 | 5.536015 | 5.535788 |
| 7 | 15000 | 5.492669 | 5.492384 | 5.491758 |
| 8 | 17000 | 5.503631 | 5.503513 | 5.502718 |
| 9 | 19000 | 5.473138 | 5.473157 | 5.472230 |
| 10 | 21000 | 5.467535 | 5.467586 | 5.466627 |
| 11 | 23000 | 5.479061 | 5.478997 | 5.478151 |
| 12 | 25000 | 5.472565 | 5.472421 | 5.471655 |
| 13 | 27000 | 5.469883 | 5.469790 | 5.468974 |
| 14 | 29000 | 5.460992 | 5.460786 | 5.460082 |
| 15 | 31000 | 5.456691 | 5.456415 | 5.455783 |
Put Options:
| Simulations | Closed Form | Euler-Maruyama | Milstein | |
|---|---|---|---|---|
| 0 | 1000 | 3.446087 | 3.444558 | 3.445297 |
| 1 | 3000 | 3.409279 | 3.406148 | 3.408488 |
| 2 | 5000 | 3.466096 | 3.462880 | 3.465290 |
| 3 | 7000 | 3.483877 | 3.481911 | 3.483063 |
| 4 | 9000 | 3.494728 | 3.492468 | 3.493912 |
| 5 | 11000 | 3.496486 | 3.494676 | 3.495670 |
| 6 | 13000 | 3.474224 | 3.472958 | 3.473412 |
| 7 | 15000 | 3.464928 | 3.463950 | 3.464116 |
| 8 | 17000 | 3.454000 | 3.452824 | 3.453190 |
| 9 | 19000 | 3.463201 | 3.462231 | 3.462389 |
| 10 | 21000 | 3.463745 | 3.462672 | 3.462934 |
| 11 | 23000 | 3.457536 | 3.456684 | 3.456726 |
| 12 | 25000 | 3.463316 | 3.462383 | 3.462504 |
| 13 | 27000 | 3.450246 | 3.449525 | 3.449435 |
| 14 | 29000 | 3.458452 | 3.457832 | 3.457639 |
| 15 | 31000 | 3.465743 | 3.464949 | 3.464930 |
# Create a trace for each scheme
closed_form_trace = go.Scatter(
x=call_results_df['Simulations'],
y=call_results_df['Closed Form'],
mode='lines',
name='Closed Form'
)
euler_maruyama_trace = go.Scatter(
x=call_results_df['Simulations'],
y=call_results_df['Euler-Maruyama'],
mode='lines',
name='Euler-Maruyama'
)
milstein_trace = go.Scatter(
x=call_results_df['Simulations'],
y=call_results_df['Milstein'],
mode='lines',
name='Milstein'
)
# Create a layout for the plot
layout = go.Layout(
title='Comparison of Call Option Payoffs for different schemes and simulation values',
xaxis=dict(title='Simulations'),
yaxis=dict(title='Option Price')
)
# Create a Figure object
fig = go.Figure(data=[closed_form_trace, euler_maruyama_trace, milstein_trace], layout=layout)
# Plot the figure
fig.show()
# Create a trace for each scheme
closed_form_trace = go.Scatter(
x=put_results_df['Simulations'],
y=put_results_df['Closed Form'],
mode='lines',
name='Closed Form'
)
euler_maruyama_trace = go.Scatter(
x=put_results_df['Simulations'],
y=put_results_df['Euler-Maruyama'],
mode='lines',
name='Euler-Maruyama'
)
milstein_trace = go.Scatter(
x=put_results_df['Simulations'],
y=put_results_df['Milstein'],
mode='lines',
name='Milstein'
)
# Create a layout for the plot
layout = go.Layout(
title='Comparison of Call Option Payoffs for different schemes and simulation values',
xaxis=dict(title='Simulations'),
yaxis=dict(title='Option Price')
)
# Create a Figure object
fig = go.Figure(data=[closed_form_trace, euler_maruyama_trace, milstein_trace], layout=layout)
# Plot the figure
fig.show()
# Update measure_time due to differences in appendix and debugging issues
def measure_time2(method, *args):
start_time = time.time()
result = method(*args)
end_time = time.time()
elapsed_time = end_time - start_time
return result, elapsed_time
def run_simulation_time(sims):
# Generate paths and measure time
CF_paths, CF_path_time = measure_time2(closed_form, S0, r, sigma, T, t_steps, sims)
EM_paths, EM_path_time = measure_time2(euler_maruyama, S0, r, sigma, T, t_steps, sims)
MS_paths, MS_path_time = measure_time2(milstein_paths, S0, r, sigma, T, t_steps, sims)
# Convert the lists of paths into DataFrames
CF_paths_df = pd.DataFrame(CF_paths).transpose()
EM_paths_df = pd.DataFrame(EM_paths).transpose()
MS_paths_df = pd.DataFrame(MS_paths).transpose()
# Calculate payoffs and measure time
CF_payoffs, CF_payoff_time = measure_time2(asian_option_payoffs, CF_paths_df, E, 'monthly')
EM_payoffs, EM_payoff_time = measure_time2(asian_option_payoffs, EM_paths_df, E, 'monthly')
MS_payoffs, MS_payoff_time = measure_time2(asian_option_payoffs, MS_paths_df, E, 'monthly')
return {
'Closed Form': {'Path': CF_path_time, 'Payoff': CF_payoff_time},
'Euler-Maruyama': {'Path': EM_path_time, 'Payoff': EM_payoff_time},
'Milstein': {'Path': MS_path_time, 'Payoff': MS_payoff_time}
}
# Define list of number of simulations
simulations = list(range(1000, 31001, 2000))
# Initialize results dictionary
results_time = {
"Scheme": [],
"Num_Sims": [],
"Path_Time": [],
"Payoff_Time": []
}
# Loop through schemes
for scheme in ["Closed Form", "Euler-Maruyama", "Milstein Scheme"]:
# Loop through number of simulations
for num_sims in simulations:
# Generate paths and measure time
start_time_paths = time.time()
if scheme == "Closed Form":
paths = closed_form(S0, r, sigma, T, t_steps, num_sims)
elif scheme == "Euler-Maruyama":
paths = euler_maruyama(S0, r, sigma, T, t_steps, num_sims)
elif scheme == "Milstein Scheme":
paths = milstein_paths(S0, r, sigma, T, t_steps, num_sims)
time_paths = time.time() - start_time_paths
# Convert the list of paths into a DataFrame
paths_df = pd.DataFrame(paths).transpose()
# Calculate payoffs and measure time
payoffs, _, payoffs_time = measure_time(asian_option_payoffs,
paths_df, E, 'monthly')
# Add computation times to results dictionary
results_time["Scheme"].append(scheme)
results_time["Num_Sims"].append(num_sims)
results_time["Path_Time"].append(time_paths)
results_time["Payoff_Time"].append(payoffs_time)
# Convert dictionary to DataFrame
results_time_df = pd.DataFrame(results_time)
# Display results
results_time_df
| Scheme | Num_Sims | Path_Time | Payoff_Time | |
|---|---|---|---|---|
| 0 | Closed Form | 1000 | 0.020747 | 0.806065 |
| 1 | Closed Form | 3000 | 0.057919 | 2.759581 |
| 2 | Closed Form | 5000 | 0.108932 | 4.841202 |
| 3 | Closed Form | 7000 | 0.119634 | 6.426707 |
| 4 | Closed Form | 9000 | 0.159325 | 8.645734 |
| 5 | Closed Form | 11000 | 0.203289 | 10.922940 |
| 6 | Closed Form | 13000 | 0.223321 | 13.016799 |
| 7 | Closed Form | 15000 | 0.263769 | 17.260853 |
| 8 | Closed Form | 17000 | 0.294461 | 18.181353 |
| 9 | Closed Form | 19000 | 0.327890 | 22.107067 |
| 10 | Closed Form | 21000 | 0.370890 | 25.375048 |
| 11 | Closed Form | 23000 | 0.404594 | 27.897599 |
| 12 | Closed Form | 25000 | 0.444528 | 30.704906 |
| 13 | Closed Form | 27000 | 0.470181 | 34.128994 |
| 14 | Closed Form | 29000 | 0.502675 | 41.517889 |
| 15 | Closed Form | 31000 | 0.553803 | 42.016981 |
| 16 | Euler-Maruyama | 1000 | 0.126859 | 0.791518 |
| 17 | Euler-Maruyama | 3000 | 0.325393 | 2.485236 |
| 18 | Euler-Maruyama | 5000 | 0.553342 | 4.323902 |
| 19 | Euler-Maruyama | 7000 | 0.795626 | 6.306914 |
| 20 | Euler-Maruyama | 9000 | 1.002682 | 8.392611 |
| 21 | Euler-Maruyama | 11000 | 1.277739 | 10.906729 |
| 22 | Euler-Maruyama | 13000 | 1.559721 | 13.074393 |
| 23 | Euler-Maruyama | 15000 | 1.734908 | 15.865952 |
| 24 | Euler-Maruyama | 17000 | 2.025306 | 18.629022 |
| 25 | Euler-Maruyama | 19000 | 2.195186 | 23.076041 |
| 26 | Euler-Maruyama | 21000 | 2.439029 | 26.219109 |
| 27 | Euler-Maruyama | 23000 | 2.632916 | 28.231555 |
| 28 | Euler-Maruyama | 25000 | 2.880464 | 31.748446 |
| 29 | Euler-Maruyama | 27000 | 3.170544 | 34.466074 |
| 30 | Euler-Maruyama | 29000 | 3.313994 | 38.334749 |
| 31 | Euler-Maruyama | 31000 | 3.697275 | 42.244707 |
| 32 | Milstein Scheme | 1000 | 0.368667 | 0.795424 |
| 33 | Milstein Scheme | 3000 | 0.765978 | 2.505959 |
| 34 | Milstein Scheme | 5000 | 1.343879 | 4.375535 |
| 35 | Milstein Scheme | 7000 | 1.837654 | 6.324358 |
| 36 | Milstein Scheme | 9000 | 2.311975 | 8.453134 |
| 37 | Milstein Scheme | 11000 | 2.791854 | 10.671279 |
| 38 | Milstein Scheme | 13000 | 3.381962 | 13.261419 |
| 39 | Milstein Scheme | 15000 | 3.862129 | 16.024965 |
| 40 | Milstein Scheme | 17000 | 4.493155 | 18.881933 |
| 41 | Milstein Scheme | 19000 | 5.099248 | 21.288825 |
| 42 | Milstein Scheme | 21000 | 5.609810 | 24.476984 |
| 43 | Milstein Scheme | 23000 | 6.133127 | 27.489787 |
| 44 | Milstein Scheme | 25000 | 6.486272 | 30.998736 |
| 45 | Milstein Scheme | 27000 | 7.130600 | 34.122734 |
| 46 | Milstein Scheme | 29000 | 7.595885 | 38.635016 |
| 47 | Milstein Scheme | 31000 | 8.399595 | 42.425363 |
# Create an empty DataFrame with the required columns
computation_time_df = pd.DataFrame(columns=['Simulation', 'Scheme', 'Path Generation', 'Payoff Calculation'])
# Loop through the results dataframe and extract relevant values
for i in range(len(results_time_df)):
scheme = results_time_df.loc[i, 'Scheme']
simulation = results_time_df.loc[i, 'Num_Sims']
path_generation_time = results_time_df.loc[i, 'Path_Time']
payoff_calculation_time = results_time_df.loc[i, 'Payoff_Time']
computation_time_data = {
'Scheme': scheme,
'Simulation': simulation,
'Path Generation': path_generation_time,
'Payoff Calculation': payoff_calculation_time
}
computation_time_df = computation_time_df.append(computation_time_data, ignore_index=True)
# Display results
#computation_time_df
closed_form_times = computation_time_df[computation_time_df['Scheme'] == 'Closed Form']
euler_maruyama_times = computation_time_df[computation_time_df['Scheme'] == 'Euler-Maruyama']
milstein_times = computation_time_df[computation_time_df['Scheme'] == 'Milstein Scheme']
merged_times = closed_form_times.merge(euler_maruyama_times, on='Simulation', suffixes=('_CF', '_EM'))
merged_times = merged_times.merge(milstein_times, on='Simulation', suffixes=('_EM', '_MS'))
# Reorder the columns
merged_times = merged_times[['Simulation', 'Path Generation_CF', 'Payoff Calculation_CF', 'Path Generation_EM', 'Payoff Calculation_EM',
'Path Generation', 'Payoff Calculation']]
# Rename the columns
merged_times.columns = ['Simulation', 'Path_CF', 'Payoff_CF', 'Path_EM', 'Payoff_EM', 'Path_MS', 'Payoff_MS']
# Display the merged dataframe
merged_times
| Simulation | Path_CF | Payoff_CF | Path_EM | Payoff_EM | Path_MS | Payoff_MS | |
|---|---|---|---|---|---|---|---|
| 0 | 1000 | 0.020747 | 0.806065 | 0.126859 | 0.791518 | 0.368667 | 0.795424 |
| 1 | 3000 | 0.057919 | 2.759581 | 0.325393 | 2.485236 | 0.765978 | 2.505959 |
| 2 | 5000 | 0.108932 | 4.841202 | 0.553342 | 4.323902 | 1.343879 | 4.375535 |
| 3 | 7000 | 0.119634 | 6.426707 | 0.795626 | 6.306914 | 1.837654 | 6.324358 |
| 4 | 9000 | 0.159325 | 8.645734 | 1.002682 | 8.392611 | 2.311975 | 8.453134 |
| 5 | 11000 | 0.203289 | 10.922940 | 1.277739 | 10.906729 | 2.791854 | 10.671279 |
| 6 | 13000 | 0.223321 | 13.016799 | 1.559721 | 13.074393 | 3.381962 | 13.261419 |
| 7 | 15000 | 0.263769 | 17.260853 | 1.734908 | 15.865952 | 3.862129 | 16.024965 |
| 8 | 17000 | 0.294461 | 18.181353 | 2.025306 | 18.629022 | 4.493155 | 18.881933 |
| 9 | 19000 | 0.327890 | 22.107067 | 2.195186 | 23.076041 | 5.099248 | 21.288825 |
| 10 | 21000 | 0.370890 | 25.375048 | 2.439029 | 26.219109 | 5.609810 | 24.476984 |
| 11 | 23000 | 0.404594 | 27.897599 | 2.632916 | 28.231555 | 6.133127 | 27.489787 |
| 12 | 25000 | 0.444528 | 30.704906 | 2.880464 | 31.748446 | 6.486272 | 30.998736 |
| 13 | 27000 | 0.470181 | 34.128994 | 3.170544 | 34.466074 | 7.130600 | 34.122734 |
| 14 | 29000 | 0.502675 | 41.517889 | 3.313994 | 38.334749 | 7.595885 | 38.635016 |
| 15 | 31000 | 0.553803 | 42.016981 | 3.697275 | 42.244707 | 8.399595 | 42.425363 |
Andreas Mavrocordatos | | LinkedIn